
文章目录
📖 开篇导读
到目前为止,我们写的所有程序都是在内存中处理数据——变量在程序运行时存在,关闭程序后就会消失。这就像在白板上写字,一擦就没。但很多场景下,我们需要持久化存储数据:用户的配置信息、程序的日志、处理后的报表、爬虫抓取的内容……这些都需要保存到文件中。
Python提供了简单而强大的内置函数open()来进行文件读写。配合上下文管理器with,可以安全、优雅地操作文件,无需担心忘记关闭。
💡 工作场景:后端程序需要读取配置文件;数据分析师读取CSV文件进行处理;日志模块将运行信息写入文件;爬虫将抓取的HTML保存到本地;自动化脚本处理Excel和文本……文件操作是每个程序员的必修课。
本课我们将学习:
- 文件的打开模式(读、写、追加、二进制等)
- 读取文件的各种方法
- 写入文件的多种方式
- 上下文管理器
with的使用 - 文件指针与定位
- 实战:文件复制、统计行数、敏感词过滤
学完本课,你将能自如地处理文本文件,实现数据的持久化存储。
🎯 学习目标
| 目标编号 | 具体掌握内容 | 对应面试/工作价值 |
|---|---|---|
| 1️⃣ | 掌握open()函数的模式和编码参数 | 正确打开各种文件 |
| 2️⃣ | 熟练使用读取方法 (read, readline, readlines) | 按需读取文件内容 |
| 3️⃣ | 熟练使用写入方法 (write, writelines) | 保存数据到文件 |
| 4️⃣ | 理解文件指针概念及seek()/tell() | 随机访问文件 |
| 5️⃣ | 掌握上下文管理器with,自动关闭文件 | 避免资源泄露 |
| 6️⃣ | 了解常见异常及处理(如FileNotFoundError) | 编写健壮的文件操作 |
🔥 面试考点:“
open文件的几种模式?”“read、readline、readlines的区别?”“为什么要用with打开文件?”“如何逐行读取大文件?”
📚 知识点理论精讲
一、文件的基本概念与打开模式
1.1 什么是文件
文件是存储在外部介质(如硬盘)上的数据集合。Python通过文件对象与操作系统进行交互,完成读写操作。
1.2 open()函数
语法:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
最常用的参数:
file:文件路径(字符串)mode:打开模式,默认'r'(只读文本)encoding:编码方式,通常'utf-8'
1.3 文件打开模式详解
| 模式 | 说明 | 文件指针位置 | 文件不存在时 |
|---|---|---|---|
'r' | 只读(默认) | 开头 | 抛出FileNotFoundError |
'w' | 只写(会覆盖原文件) | 开头 | 创建新文件 |
'a' | 追加(末尾添加) | 末尾 | 创建新文件 |
'x' | 独占创建,如果文件已存在则报错 | 开头 | 创建新文件 |
'b' | 二进制模式(与r/w/a等组合,如'rb') | 依据主模式 | 同上 |
't' | 文本模式(默认,可与r/w/a组合,如'rt') | 依据主模式 | 同上 |
'+' | 读写模式(如'r+') | 依据主模式 | 依据主模式 |
常用组合:
'r':只读文本(最常用)'w':只写文本,覆盖已有内容'a':追加文本'rb':读取二进制文件(如图片、音频)'wb':写入二进制文件'r+':读写(指针在开头)'w+':读写,覆盖或创建'a+':读和追加,指针在末尾
🧠 通俗类比:打开文件就像打开一本笔记本。
'r'(只读):你只能看,不能写。如果笔记本不存在,会报错。'w'(只写):你可以写,但会先把原来内容清空(如果笔记本存在)或新建一本。'a'(追加):你在最后面续写,不会破坏已有内容。'r+':你既可以看,也可以修改已有内容。
1.4 编码问题
文本文件有编码(如UTF-8、GBK)。Python默认使用平台编码(Windows上通常是GBK,Mac/Linux是UTF-8)。为了避免乱码,建议显式指定encoding='utf-8'。
# 正确方式
f = open('data.txt', 'r', encoding='utf-8')
二、读取文件的方法
2.1 read(size=-1)
读取指定字节数(文本模式下是字符数)。不传参数或负数,读取整个文件。
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read() # 读取全部
print(content)
注意:大文件不要使用read()不带参数,会耗尽内存。应逐行或分块读取。
2.2 readline()
读取一行(包括换行符\n)。到达文件末尾时返回空字符串。
with open('data.txt', 'r', encoding='utf-8') as f:
line = f.readline()
while line:
print(line.strip())
line = f.readline()
2.3 readlines()
读取所有行,返回字符串列表(每行包括换行符)。
with open('data.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
print(line, end='')
2.4 直接迭代文件对象(推荐)
文件对象本身是可迭代的,可以直接for line in f:逐行读取,内存友好。
with open('data.txt', 'r', encoding='utf-8') as f:
for line in f: # 逐行读取,不会一次加载整个文件
print(line.strip())
💡 工作应用:处理大型日志文件时,必须使用逐行迭代,而不能用
read()。
三、写入文件的方法
3.1 write(str)
将字符串写入文件。返回写入的字符数(可用于校验)。不会自动添加换行符。
with open('output.txt', 'w', encoding='utf-8') as f:
f.write("Hello\n")
f.write("World\n")
3.2 writelines(lines)
写入一个字符串列表,也不会自动添加换行符。
lines = ["第一行\n", "第二行\n", "第三行\n"]
with open('output.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
3.3 追加模式
使用'a'模式,在文件末尾追加内容。
with open('log.txt', 'a', encoding='utf-8') as f:
f.write("新的日志条目\n")
3.4 写入非字符串数据
需要先转换为字符串。
data = [1, 2, 3]
with open('nums.txt', 'w') as f:
f.write(str(data)) # 写入 "[1, 2, 3]"
四、上下文管理器:with语句
4.1 为什么需要with
传统打开文件需要手动关闭,容易遗漏导致资源泄露。
# 不推荐
f = open('data.txt', 'r')
content = f.read()
f.close() # 容易忘记
如果read()过程中发生异常,close()可能不会执行。
4.2 with的作用
with语句创建一个上下文环境,进入时执行__enter__(打开文件),退出时自动执行__exit__(关闭文件),即使发生异常也会关闭。
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 此处文件已自动关闭,无需手动close
最佳实践:始终使用with打开文件。
4.3 多个文件的with
with open('input.txt', 'r') as f_in, open('output.txt', 'w') as f_out:
data = f_in.read()
f_out.write(data.upper())
五、文件指针与随机访问
文件指针指示当前读写位置。可以使用tell()获取当前位置,seek()移动指针。
5.1 tell():返回当前指针位置(字节数)
with open('data.txt', 'r', encoding='utf-8') as f:
print(f.tell()) # 0
f.read(5)
print(f.tell()) # 5(如果每个字符1字节)
5.2 seek(offset, whence)
移动指针到指定位置。
whence=0(默认):相对于文件开头whence=1:相对于当前位置whence=2:相对于文件末尾
with open('data.txt', 'r', encoding='utf-8') as f:
f.seek(10) # 移动到第10字节
f.seek(5, 1) # 当前位置往后5字节
f.seek(-10, 2) # 从末尾往前10字节
⚠️ 注意:文本模式下,
seek的偏移量必须是由tell()返回的位置(因为编码字符长度不固定)。随机访问建议使用二进制模式。
六、常见异常处理
| 异常 | 触发条件 | 处理方式 |
|---|---|---|
FileNotFoundError | 以'r'模式打开不存在的文件 | 检查路径或先创建 |
PermissionError | 无权限访问文件 | 修改权限或换路径 |
UnicodeDecodeError | 编码不匹配 | 指定正确的encoding |
IsADirectoryError | 试图打开目录作为文件 | 检查路径是否为文件 |
try:
with open('missing.txt', 'r') as f:
content = f.read()
except FileNotFoundError:
print("文件不存在,请检查路径")
except PermissionError:
print("没有读取权限")
七、二进制文件操作
对于图片、音频、视频等非文本文件,使用二进制模式'rb'或'wb'。
# 复制图片
with open('source.jpg', 'rb') as src:
data = src.read()
with open('copy.jpg', 'wb') as dst:
dst.write(data)
二进制模式不需要指定编码。
💻 代码案例实操
案例1:基础文件读写——写一首诗并读取
"""
poem_file.py
演示基础的写文件和读文件
"""
# 写入文件
poem = """床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。"""
with open('poem.txt', 'w', encoding='utf-8') as f:
f.write(poem)
print("诗歌已写入 poem.txt")
# 读取整个文件
with open('poem.txt', 'r', encoding='utf-8') as f:
content = f.read()
print("\n=== 完整内容 ===")
print(content)
# 逐行读取
print("\n=== 逐行读取 ===")
with open('poem.txt', 'r', encoding='utf-8') as f:
for line in f:
# strip() 去掉每行末尾的换行符
print(f"行内容: {line.strip()}")
案例2:文件追加与日志记录
"""
log_writer.py
模拟日志写入,演示追加模式
"""
import datetime
def write_log(message, log_file='app.log'):
"""将带时间戳的消息追加到日志文件"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"[{timestamp}] {message}\n")
# 模拟多个日志
write_log("程序启动")
write_log("用户登录成功")
write_log("执行任务A")
write_log("程序正常退出")
# 读取并显示日志
print("=== 当前日志内容 ===")
with open('app.log', 'r', encoding='utf-8') as f:
for line in f:
print(line, end='')
案例3:读取大文件的正确姿势(逐行迭代)
"""
large_file_reader.py
演示如何高效读取大文件(避免内存爆炸)
"""
def count_lines(filename):
"""统计文件行数(逐行读取,不占内存)"""
count = 0
with open(filename, 'r', encoding='utf-8') as f:
for line in f: # 逐行迭代
count += 1
return count
def search_keyword(filename, keyword):
"""搜索包含关键词的行"""
matches = []
with open(filename, 'r', encoding='utf-8') as f:
for line_no, line in enumerate(f, 1):
if keyword in line:
matches.append((line_no, line.strip()))
return matches
# 模拟处理大文件(如果文件很大,不会全部读入内存)
# 实际使用时,可以传入大型日志文件
if __name__ == "__main__":
# 先创建一个小测试文件
with open('test_large.txt', 'w', encoding='utf-8') as f:
for i in range(1000):
f.write(f"这是第{i}行,内容是一些文本。\n")
line_count = count_lines('test_large.txt')
print(f"总行数: {line_count}")
results = search_keyword('test_large.txt', '500')
print(f"找到 {len(results)} 行包含'500':")
for line_no, line in results[:3]:
print(f" 行{line_no}: {line}")
案例4:文件复制与异常处理
"""
file_copy.py
实现文件复制功能,并处理常见异常
"""
import os
def copy_file(src, dst):
"""复制文件从 src 到 dst"""
try:
# 检查源文件是否存在
if not os.path.exists(src):
raise FileNotFoundError(f"源文件不存在: {src}")
# 以二进制模式读取和写入,支持任何类型文件
with open(src, 'rb') as f_src:
data = f_src.read()
with open(dst, 'wb') as f_dst:
f_dst.write(data)
print(f"复制成功: {src} -> {dst}")
return True
except FileNotFoundError as e:
print(f"错误: {e}")
except PermissionError as e:
print(f"权限错误: {e}")
except Exception as e:
print(f"未知错误: {e}")
return False
# 测试
copy_file('poem.txt', 'poem_copy.txt')
copy_file('not_exist.txt', 'copy.txt')
案例5:文件指针操作与tell/seek
"""
file_pointer_demo.py
演示文件指针移动和随机访问
"""
# 准备示例文件
with open('test.txt', 'w', encoding='utf-8') as f:
f.write("0123456789")
f.write("ABCDEFGHIJ")
# 读取并观察指针位置
with open('test.txt', 'r', encoding='utf-8') as f:
print(f"初始位置: {f.tell()}")
data = f.read(5)
print(f"读取5个字符: '{data}', 当前位置: {f.tell()}")
# 跳到第10个字符处
f.seek(10)
print(f"seek(10)后位置: {f.tell()}")
data2 = f.read(5)
print(f"读取5个字符: '{data2}'")
# 从当前位置偏移+2
f.seek(2, 1) # 1表示相对当前位置
print(f"seek(2,1)后位置: {f.tell()}")
data3 = f.read(3)
print(f"读取3个字符: '{data3}'")
# 从末尾往前5个字节(注意文本模式下谨慎使用)
# f.seek(-5, 2) # 可能出错,因为文本模式编码不定长
# 二进制模式下的seek更可靠
with open('test.txt', 'rb') as f:
f.seek(-5, 2) # 从末尾往前5个字节
last_bytes = f.read()
print(f"最后5字节内容: {last_bytes}")
案例6:配置文件读取与修改
"""
config_reader.py
模拟读取和修改配置文件(类似INI格式)
"""
# 假设有一个配置文件 config.txt
# 格式: key = value
def read_config(filename):
"""读取配置文件,返回字典"""
config = {}
try:
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip()
except FileNotFoundError:
print(f"配置文件 {filename} 不存在,将创建新文件")
return config
def write_config(filename, config):
"""将配置字典写入文件"""
with open(filename, 'w', encoding='utf-8') as f:
f.write("# 配置文件\n")
for key, value in config.items():
f.write(f"{key} = {value}\n")
print(f"配置已保存到 {filename}")
# 使用示例
config_file = 'myapp.ini'
# 读取现有配置
config = read_config(config_file)
print("当前配置:", config)
# 修改或添加配置
config['username'] = 'admin'
config['debug'] = 'true'
config['timeout'] = '30'
# 写回文件
write_config(config_file, config)
# 再次读取验证
new_config = read_config(config_file)
print("更新后配置:", new_config)
案例7:多文件同时操作——合并文件
"""
file_merger.py
合并多个文本文件的内容到一个文件
"""
def merge_files(input_files, output_file):
"""将多个文件按顺序合并到一个输出文件"""
with open(output_file, 'w', encoding='utf-8') as out_f:
for file_path in input_files:
try:
with open(file_path, 'r', encoding='utf-8') as in_f:
out_f.write(f"--- 文件: {file_path} ---\n")
out_f.write(in_f.read())
out_f.write("\n\n") # 间隔
print(f"已合并: {file_path}")
except FileNotFoundError:
print(f"警告: 文件 {file_path} 不存在,跳过")
# 准备测试文件
for i in range(1, 4):
with open(f'part{i}.txt', 'w', encoding='utf-8') as f:
f.write(f"这是第{i}部分的内容。\n" * 3)
# 合并
merge_files(['part1.txt', 'part2.txt', 'part3.txt'], 'merged.txt')
# 查看结果
print("\n=== 合并后的文件内容 ===")
with open('merged.txt', 'r', encoding='utf-8') as f:
print(f.read())
⚠️ 易错点避坑总结
| 序号 | 坑点描述 | 后果 | 解决方案 |
|---|---|---|---|
| 1 | 忘记指定encoding,导致跨平台兼容问题 | 中文乱码或UnicodeDecodeError | 明确使用encoding='utf-8' |
| 2 | 使用'w'模式时误删原有内容 | 文件被覆盖,数据丢失 | 需要保留原内容用'a'或先读取再写入 |
| 3 | 大文件用read()不带参数 | 内存溢出 | 逐行迭代或分块读取 |
| 4 | 忘记关闭文件(不用with) | 资源泄露,可能写不完整 | 始终使用with语句 |
| 5 | 文本模式下使用seek随机访问 | 位置错误或因编码报错 | 二进制模式使用seek,文本模式少用 |
| 6 | 写入时忘记添加换行符\n | 所有内容挤在一行 | 显式添加\n或使用print(file=f) |
| 7 | 路径中使用反斜杠\ | 转义错误(如\t、\n) | 使用原始字符串r"path"或正斜杠/ |
| 8 | 异常处理过于宽泛 | 隐藏其他错误 | 捕获具体异常(如FileNotFoundError) |
| 9 | 同时读写同一个文件时未使用+模式 | 写入后读不到新内容(指针位置问题) | 正确使用seek或分开操作 |
| 10 | 二进制文件写入字符串(忘记encode) | TypeError | 二进制模式写入bytes,或使用wb+encode |
📝 课后实战练习题
第1题:基本读写
编写程序,创建一个todo.txt文件,写入以下内容:
买菜
写报告
学习Python
然后读取并逐行打印,每行前显示行号(从1开始)。
第2题:日志分析
假设有一个access.log文件,每行格式:时间 IP 请求方法 状态码。编写程序:
- 统计状态码为404的行数
- 找出所有IP地址(去重)
- 输出次数最多的IP地址
(你可以先创建一个小型测试日志文件,包含10-20行模拟数据)
第3题:文件追加与搜索
编写一个电话本管理程序:
- 支持添加联系人(姓名,电话)到
contacts.txt,格式:姓名|电话 - 支持根据姓名查找并显示电话
- 支持删除联系人(先读取全部,删除匹配行后再写回)
第4题:大文件处理
生成一个包含100000行的大文件(每行一个随机整数),然后编写程序:
- 统计文件中所有数字的总和
- 找出最大值和最小值
- 要求使用逐行读取,不能一次性读入内存
第5题:复制二进制文件
编写一个函数copy_binary(src, dst, chunk_size=1024),使用分块读写(每次读取chunk_size字节)来复制文件,适用于任意大小文件。对比一次性read()和分块读写的效率(可选)。
第6题:CSV格式读取
假设有一个students.csv文件,内容如下:
姓名,年龄,分数
张三,18,85
李四,19,92
王五,18,78
编写程序读取该文件,将每行转换为字典(如{"姓名":"张三","年龄":18,"分数":85}),然后计算平均分数。
第7题:文件加密简单版
编写一个程序,读取文本文件plain.txt,对每个字符进行简单的移位加密(如每个字母后移3位,非字母不变),将加密结果写入encrypted.txt。再编写一个解密程序,将encrypted.txt恢复为原内容。
🔜 下节课预告
掌握了文本文件的基础操作后,下一节课我们将深入文件处理的进阶内容:二进制文件、路径处理以及批量文件操作。
第17课:文件进阶:二进制文件、路径处理与批量文件操作实战
内容包括:
- 二进制文件的读写(图片、音频、视频)
os和pathlib模块处理路径- 文件和目录的创建、删除、重命名
glob和os.walk批量遍历文件- 实战:批量重命名文件、按扩展名分类、自动备份
文件操作与系统交互是自动化脚本的核心,学完你将能编写实用的文件管理工具。
🌟 学习鼓励:文件操作是编程中非常实用的技能。你可能会遇到各种编码错误、路径问题,但请耐心调试——多练习几次,你就会掌握其中的规律。特别是
with语句,一定要养成习惯。下一课的批量文件操作会让你的工作效率倍增!
🔗《50节课 Python 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
531

被折叠的 条评论
为什么被折叠?



