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+ | 读写追加 | 末尾追加 | 创建新文件 |
你想想看,最常用的是哪个?r和a。读历史数据用r,写日志用a。千万别随便用w,它会清空文件!
我曾经在调试时手滑写了个w模式,结果把存了一周的温室温度数据全清空了。从那以后,我写文件前都会先确认模式,尤其是w和w+,用之前一定三思。
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,你的代码会稳很多。