4、Python文件操作:open()函数、文件读写模式、with语句上下文管理、文件指针与seek

好,咱们进入第四章。这一章讲的是Python文件操作,说白了就是怎么把数据存到硬盘上,再读回来。

做物联网的,天天跟文件打交道。传感器数据要落盘,历史记录要回放,配置文件要读写。我刚开始做温室项目时,就吃过文件没关好的亏——程序跑了一整天,结果数据全丢了。嗯,从那以后,我对文件操作就格外上心。

4.1 open()函数:打开文件的第一道门

Python里操作文件,第一步就是调用open()。它的基本用法很简单:

file_object = open('filename', 'mode')

第一个参数是文件名,第二个是模式。返回一个文件对象,后面所有操作都靠它。

我个人习惯把文件名写成绝对路径,尤其是在生产环境里。为什么?因为相对路径容易搞混,你写个data.txt,程序跑起来后到底在哪个目录找?我踩过这个坑——有一次部署脚本,路径没配好,程序一直在读一个空文件,查了半天才发现。

小技巧:os.path.join()拼接路径,跨平台兼容,不会出现反斜杠正斜杠的问题。

4.2 文件读写模式:r、w、a、r+、w+、a+

模式这块,说白了就是告诉Python:你想怎么折腾这个文件?

模式 含义 文件存在时 文件不存在时
r 只读 正常打开 报错
w 只写 清空内容 创建新文件
a 追加 末尾追加 创建新文件
r+ 读写 正常打开 报错
w+ 读写 清空内容 创建新文件
a+ 读写追加 末尾追加 创建新文件

你想想看,最常用的是哪个?ra。读历史数据用r,写日志用a。千万别随便用w,它会清空文件!

我曾经在调试时手滑写了个w模式,结果把存了一周的温室温度数据全清空了。从那以后,我写文件前都会先确认模式,尤其是ww+,用之前一定三思。

警告:w模式会直接清空文件内容,没有确认提示!如果你只是想追加数据,请用a模式。

4.3 with语句:自动关文件的省心写法

以前写文件操作,要手动file.close()。但人总会忘,一忘就出问题。文件没关好,数据可能没写完,或者文件被占用打不开。

with语句就是来解决这个问题的。它叫上下文管理器,说白了就是:你只管用,用完了它自动帮你收拾。

# 传统写法
f = open('data.txt', 'r')
content = f.read()
f.close()  # 容易忘

# with写法
with open('data.txt', 'r') as f:
    content = f.read()
# 到这里自动关闭,不用操心

我个人强烈建议:所有文件操作都用with。没有例外。为什么?因为省心、安全、代码也干净。你想想看,要是程序中途报错了,传统写法里close()可能根本执行不到,文件就一直开着。但with语句,就算报错也会帮你关掉。

核心原则:能用with就别手动open/close。这是Python社区的共识,也是我踩坑后的血泪教训。

4.4 文件指针:你在文件里的当前位置

文件指针这个概念,刚开始接触时容易懵。其实很简单——它就像一个光标,标记着你当前读到或写到了文件的哪个位置。

打开文件时,指针默认在开头(0的位置)。读一次文件,指针就往后移动。再读,就从上次停下的地方继续。

with open('data.txt', 'r') as f:
    line1 = f.readline()  # 读第一行,指针移到第一行末尾
    line2 = f.readline()  # 读第二行,指针继续移动
    # 指针位置可以通过 f.tell() 查看
    print(f.tell())  # 输出当前指针位置(字节数)

我记得有一次做数据回放,需要反复读取同一个文件的不同部分。一开始没注意指针位置,结果第二次读的时候啥也没读到——因为指针已经跑到文件末尾了。嗯,这就是指针的坑。

4.5 seek():让指针跳来跳去

seek()就是用来控制指针位置的。它的语法是:

file.seek(offset, whence)
  • offset:偏移量,正数向后,负数向前
  • whence:参考位置,0表示文件开头,1表示当前位置,2表示文件末尾

举个例子:

with open('data.txt', 'r') as f:
    # 跳到文件开头
    f.seek(0, 0)
    
    # 跳到第100个字节
    f.seek(100, 0)
    
    # 从当前位置往后跳50个字节
    f.seek(50, 1)
    
    # 跳到文件末尾前200个字节
    f.seek(-200, 2)

在物联网场景里,seek()特别有用。比如你要读取一个日志文件的最后几行,就可以先seek(-1024, 2)跳到末尾前1KB的位置,然后读出来再解析。这样不用把整个大文件都加载到内存里。

实战经验:处理大文件时,用seek()配合read()分段读取,比一次性读入内存高效得多。我处理过几百MB的温室历史数据,就是这么干的。

4.6 综合示例:温室数据文件读写

最后,咱们把今天学的串起来,写一个完整的例子。假设我们要把传感器数据写入文件,然后再读出来回放:

# 写入温室数据
with open('greenhouse_data.csv', 'a') as f:
    f.write('2024-01-15 14:30:00,25.3,65.2,1200\n')
    f.write('2024-01-15 14:31:00,25.4,65.1,1198\n')

# 读取并回放
with open('greenhouse_data.csv', 'r') as f:
    # 跳到文件末尾前200字节,读取最后几条记录
    f.seek(-200, 2)
    last_chunk = f.read()
    print('最近的数据:')
    print(last_chunk)

# 重新从开头读取全部数据
with open('greenhouse_data.csv', 'r') as f:
    for line in f:
        timestamp, temp, humidity, light = line.strip().split(',')
        print(f'{timestamp} - 温度:{temp}°C, 湿度:{humidity}%, 光照:{light}lux')

你看,with保证了文件自动关闭,seek让我们能灵活定位,a模式确保不会覆盖历史数据。这一套组合拳,在实战中非常实用。

好了,这一章就到这里。记住:文件操作看似简单,但细节决定成败。一个没关好的文件,可能让你一整天的数据白干。用with,用对模式,用好seek,你的代码会稳很多。