第十章 Python之文件写操作
| | |
|---|
读写主模式 | | 只读(默认)。文件必须存在,否则报错 FileNotFoundError。 |
| | 只写。文件存在则清空(截断),不存在则新建。危险操作,注意备份! |
| | 独占创建。写模式,但文件已存在则抛出 FileExistsError,防止覆盖。 |
| | 追加。文件存在则末尾追加,不存在则新建。无法读取,只能写。 |
格式模式 | | 文本模式(默认)。读写字符串 str,需指定 encoding。 |
| | 二进制模式。读写字节流 bytes(如图片、音频),禁止传 encoding 参数。 |
附加标识 | | 更新模式(读写兼得)。搭配使用:r+(读写,指针在开头)、w+(清空读写)、a+(追加读写,指针在末尾)。 |
1. 写入文件
Python中写入文本文件有三种常用模式,区别在于如何处理已存在的文件和写入位置。
1.1 w模式(写入,覆盖)
- 行为:打开文件用于写入。
若文件已存在,会先清空(截断)整个文件;若不存在则新建。 - 注意:每次打开都会丢失原有内容,适合从头生成新内容。
# open 函数最常用的三个参数如下:
# 1.file:要操作的文件路径
# 2.mode:文件的打开模式
# r :读取(默认值)
# w :写入,并先截断文件
# x :排它性创建,如果文件已存在,则创建失败
# a :打开文件用于写入,如果文件存在,则在文件末尾追加内容
# b :二进制模式
# t :文本模式(默认值)
# + :打开用于更新(读取与写入)
# 3.encoding:字符编码
# 测试 w 模式
with open('demo.txt', 'wt', encoding='utf-8') as file:
file.write('Hello Python!')
💡 频繁使用 w 时,务必确认文件路径,避免误清空重要数据。
1.2 x模式(排他创建,安全写入)
- 行为:
仅当文件不存在时才创建并写入;若文件已存在,立即抛出 FileExistsError,避免意外覆盖。 - 适用场景:日志归档、配置文件初始化等
确保不覆盖已有文件的需求。
# 测试 x 模式
with open('demo2.txt', 'xt', encoding='utf-8') as file:
file.write('Hello Python!')
⚠️ 需要配合异常处理(try-except)来优雅处理文件已存在的情况。
1.3 a 模式(追加,保留历史)
行为:打开文件用于写入,文件指针自动移至末尾。若文件存在,新内容追加在最后;若不存在则新建。适用场景:日志记录、持续收集数据,希望保留所有历史写入。
# 测试 a 模式
with open('demo2.txt', 'at', encoding='utf-8') as file:
file.write('Hello Python!'
🔄 a 模式不会清空文件,多次写入的内容会按顺序累积。
1.4 快速对照表
选型建议:
所有模式都建议使用 with 语句,确保文件自动关闭,避免资源泄漏。若操作二进制文件(如图片),将模式中的 t 改为 b(如 'wb'、'xb'、'ab')。
1.5 flush 方法(强制落盘)
- 概述:Python 写入文件时,为提升性能,不会每次调用
write() 都直接操作硬盘,而是先将数据积攒在内存的缓冲区中,待缓冲区满或文件关闭时才统一写入。 flush() 作用:立即将缓冲区中积攒的所有数据强制写入磁盘文件,不等缓冲区满或文件关闭。
# 在 Python 中文件写入时,并不是每写一次就立刻落盘,而是:先写到“缓冲区”里。
# 文件对象的 flush 方法:把缓冲区中的数据,立刻写入到文件中。
import time
with open('demo.txt', 'at', encoding='utf-8') as file:
file.write('张三')
file.write('李四') # 此时还在缓冲区,未落盘
file.flush() # 强制落盘,此时文件中已存在"张三李四"
time.sleep(10000) # 长眠期,打开文件可见前两句,但看不到后两句
file.write('王五')
file.write('赵六') # 程序结束或缓冲区满时,这两句才会真正写入
💡 在 sleep 期间查看 demo.txt,你会看到前两句已保存,而后两句仍在内存中——这就是 flush 的即时生效效果。
🧠 核心补充:
自动刷新时机:文件被 close() 关闭时,会自动调用 flush(),因此正常情况下无需手动调用。性能权衡:flush() 会触发磁盘 I/O,频繁调用会显著降低写入速度,建议仅在关键节点使用。
⚠️ 与 close() 的关系:
close() 内部已包含 flush(),所以关闭后无需再单独调用。如果代码中使用了 with 语句,退出上下文时会自动关闭并刷新,通常无需额外操作。
2. 组合模式
+ 表示可读可写,但具体行为(指针位置、是否清空)仍由基础模式(r / w / x / a)决定。t 为文本默认值,可省略(如 'r+' 即 'rt+')。
2.1 rt+(读写,从头开始)
- 行为:文件必须存在。
指针初始在开头,写入会从当前位置覆盖字符。 - 注意:写入后若需读取刚写的内容,必须先用
seek 移回开头;文本模式下seek偏移量请谨慎使用(中文字符占多字节,随意定位会破坏编码)。
# 测试 rt+
with open('a.txt', 'rt+', encoding='utf-8') as file:
# seek(offset, whence)方法:用于改变文件对象指针的位置,参数说明如下:
# offset:偏移量,要移动多少距离
# whence:参考点,从哪里开始计算偏移,有三种取值:
# 0:从文件开头计算(默认值)
# 1:从当前位置计算
# 2:从文件末尾计算
# 注意:在文本模式下,不要随意去定位中文字符位置,否则可能破坏文件编码。
file.seek(0, 0)
file.write('你好')
2.2 wt+(读写,先清空)
- 行为:文件存在则
清空(截断),不存在则新建。指针初始在开头。w模式可以写入,+模式可以用于更新(读取或写入),所以wt+模式可读可写。 - 关键点:执行
write 后,指针会移动到末尾。想读取刚写入的内容,必须先 seek(0) 重置指针。w模式打开文件后,文件指针在起始位置,但write方法执行完后,指针在文件结束位置。由于t是默认值,所以wt+中的t可以省略。
with open('a.txt', 'wt+', encoding='utf-8') as file:
file.write('你好') # 写入后指针在末尾
file.seek(0) # 必须移回开头才能读取
result = file.read()
print(result)
2.3 xt+(读写,排他创建)
- 行为:文件
不存在则创建并允许读写;若已存在则报错 FileExistsError。指针初始在开头。x模式打开文件后,文件指针在起始位置。
with open('demo3.txt', 'xt+', encoding='utf-8') as file:
file.write('你好')
file.seek(0) # 移回开头再读取
result = file.read()
print(result)
2.4 at+(读写,强制追加)
- 行为:文件存在则
指针初始在末尾,不存在则新建。调用 write 永远追加到文件末尾,即使你手动用 seek 移动了指针,写入时依然会强制跳回末尾。at模式可以追加内容,+模式可以用于更新(读取或写入),所以at+模式可读可写。at模式打开文件后,文件指针在结束位置。
with open('a.txt', 'at+', encoding='utf-8') as file:
file.write('你好') # 默认追加到末尾
file.seek(0) # 必须移回开头才能读取已有全文
result = file.read()
print(result)
🔥 避坑:at+ 模式下,seek 只能影响读取位置,对写入位置无效(写入永远追加)。因此若想在文件中间修改内容,请改用 r+。
快速对比(关键差异):
选型建议:
⚠️ 读写混合模式容易混淆,牢记写后读必 seek(0)口诀,避免读到空数据或乱码。
3. 目录操作(os / shutil)
Python 通过 os 和 shutil 模块提供目录管理能力,核心分为创建、删除、判断和遍历四类。
3.1 创建目录
os.mkdir(path):创建单级目录。若父目录不存在或目录已存在,抛出 FileNotFoundError / FileExistsError。os.makedirs(path):创建多级目录(自动创建缺失的父级目录)。若全部已存在则报错。
import os
os.mkdir('D:/demo') # 仅一级
os.makedirs('D:/demo/aa/bb') # 自动创建 demo → aa → bb
💡 makedirs 还可传 exist_ok=True(Python 3.2+)使其在目录已存在时不报错,更安全。
3.2 删除目录
os.rmdir(path):删除空目录。目录非空或不存在均报错。os.removedirs(path):递归删除空目录。从最末一级开始向上删除,直到遇到非空目录即停止。shutil.rmtree(path):强制删除整个目录树(含所有子目录和文件),危险操作,谨慎使用。
os.rmdir('D:/demo/aa/bb') # 删除 bb(必须为空)
os.removedirs('D:/demo/aa/bb') # 先删 bb,若 aa 变空则继续删 aa,依此类推
shutil.rmtree('D:/demo') # 直接删除整个 demo,不可恢复
3.3 路径判断(路径存在性 & 类型)
| |
|---|
os.path.exists(path) | |
os.path.isdir(path) | |
os.path.isfile(path) | |
python
print(os.path.exists('D:/demo/aa/bb')) # False(若不存在)
print(os.path.isdir('D:/demo')) # True(若为目录)
print(os.path.isfile('D:/demo')) # False(目录不是文件)
3.4 遍历目录(轻量 & 递归)
os.scandir(path):返回迭代器,遍历单层目录项。比 os.listdir 更高效(直接获取文件类型信息),适合一层扫描。
with os.scandir('D:/demo') as entries: # 建议用 with 管理资源
for entry in entries:
print('目录'if entry.is_dir() else'文件', entry.name)
os.walk(path):递归生成三元组 (当前目录路径, 子目录列表, 文件名列表),按层级逐层遍历整个目录树。
for dirpath, dirnames, filenames in os.walk('D:/demo'):
print(f'当前目录:{dirpath}')
print(f'子目录:{dirnames}')
print(f'文件:{filenames}')
⚠️危险操作提醒:
shutil.rmtree 直接删除整个目录树,·无回收站·,执行前务必确认路径(建议先用 os.path.exists 检查)。- 若需删除非空目录更安全,可先遍历删除文件,再删空目录,但
rmtree 仍是首选(注意备份)。
# ⚠️危险操作:删除有内容的目录
# shutil.rmtree('D:/demo')
📌 场景选型速查:
- 删空目录 →
rmdir 或 removedirs - 判断路径类型 →
exists / isdir / isfile
4. 综合小练习
练习1:将一个二进制文件复制到指定位置。
# 练习1:将一个二进制文件复制到指定位置。
# region
import os
# 源文件
source = 'music.mp3'
# 目标目录
target = 'D:/media'
# 如果目标目录不存在,那就去创建
if not os.path.isdir(target):
os.makedirs(target)
with open(source, 'rb') as f1, open(target + '/' + 'my_music.mp3', 'wb') as f2:
while True:
# 每次只读取1KB
data = f1.read(1024)
# 如果文件读取完毕了,就跳出循环
if not data:
break
# 向目标文件中写入数据
f2.write(data)
print('复制完毕')
# endregion
练习 2:日志记录,需求如下:
- 用户名存在,但密码错误,提示“密码错误”,并记录日志。
- 用户名和密码均正确,提示“登录成功”,并记录日志。
# 练习2:日志记录。
# 1.用户输入用户名和密码后,程序进行校验:
# 2.用户名不存在,提示“用户名未注册”,并记录日志。
# 3.用户名存在,但密码错误,提示“密码错误”,并记录日志。
# 4.用户名和密码均正确,提示“登录成功”,并记录日志。
import time
# 准备一些用户
users = {
'张三': '123456',
'李四': '888888',
'王五': 'abc123'
}
# 提示输入信息
username = input('请输入用户名:')
password = input('请输入密码:')
# 获取当前的时间
now = time.strftime('%Y-%m-%d %H:%M:%S')
# 如果用户名不在users中
if username notin users:
print('用户名未注册')
with open('log.txt', 'at', encoding='utf-8') as file:
file.write(f'{now} {username} 登录失败(用户未注册)\n')
# 如果密码不正确
elif users[username] != password:
print('密码不正确')
with open('log.txt', 'at', encoding='utf-8') as file:
file.write(f'{now} {username} 密码错误 \n')
# 登录成功
else:
print('登录成功!')
with open('log.txt', 'at', encoding='utf-8') as file:
file.write(f'{now} {username} 登录成功 \n')