第十章 Python之文件读操作
1. 文件的分类
无论是图片还是文档,硬盘里都是 0 和 1。唯一的区别在于:软件读取这串 0 和 1 时,用的是哪本“翻译词典”?
1.1 纯文本文件
存储逻辑:逐字对照编码表,把字符(字母、汉字)转为二进制;读取时反向解码为文字。致命特征:极度“纯粹”——只存文字内容,绝对不存字体、颜色、大小等任何格式指令。呈现结果:任何基础编辑器(记事本)都能直接打开,呈现人类可读的纯文字。常见后缀:.txt、.py、.md、.html(源码层面仍是纯字符)。
1.2 二进制文件
- 规则 = “私有格式规范”(如图片压缩算法、音频采样协议).
存储逻辑:不关心字符编码,而是按照特定格式把结构体(像素点、采样率、章节偏移量)直接转为二进制。致命特征:结构复杂`——内部数据有特定长度和位置含义(如“前4字节是宽度,后4字节是高度”).呈现结果:必须由专用软件按协议解析,结果是图片、视频或压缩包;强行用记事本打开会变乱码。常见后缀:.jpg、.mp3、.docx(本质是 ZIP 压缩包,属二进制)、.exe。
1.3 二者区分
用系统自带的记事本打开,保存后再关闭,文件还能正常使用吗?
能正常用(内容没丢、没损坏) → 纯文本文件(因为记事本只改字符,不改结构)。完全报废(打不开、乱码或损毁) →二进制文件(因为记事本强行插入了 UTF-8 字符标记,破坏了原有的私有格式结构)。
误区:
- 很多人误以为
.docx 或 .xlsx 是文本,因为它们有字——大错特错。它们是ZIP 压缩包(二进制),里面虽然包裹着 XML 文本片段,但整个文件必须由 Office 拆包解析,所以归属二进制。后缀永远骗人,解析规则才是唯一真理。
2. 绝对路径vs相对路径
核心本质:路径就是寻址。两者的唯一区别在于我从哪儿开始找。
2.1 绝对路径
- 从文件系统的
最顶层(即“根”)出发,逐级向下,完整无遗漏地描述目标位置。 关键特征:像身份证号一样,在整台电脑中唯一且不变。Windows:以盘符为根(如 C:\ 或 D:\)。Linux / macOS:以斜杠 / 为根(没有盘符概念)
2.2 相对路径
- 以你
此时此刻所在的文件夹为参照物,描述目标的相对位置。 关键特征:像导航语音,结果随“我当前在哪”而变化。你必须搞懂两个特殊符号:
2.3 二者区分
绝对路径管绝对,相对路径看相对。
何时用绝对:写系统服务、定时任务(Crontab)或引用外部固定资源时。因为环境不可控,用绝对路径最稳妥,防止找不到文件。何时用相对:写项目代码时。因为项目文件夹可能会被拷贝到别人的电脑(盘符变了),使用相对路径可以保证代码零修改直接运行(即“可移植性”)。
3. Python操作文件的标准流程
标准流程成如下:
Python中可以只使用两步完成:
- 使用
with 语句创建上下文(自动管理资源,退出即关闭,无需手动 close())。
# 正确姿势(自动关闭)
with open('test.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 退出缩进后,文件句柄自动释放
3.1 open函数
- 支持
绝对路径(如 D:/data/a.txt)和相对路径(如 ./a.txt)。 跨平台建议:路径字符串前加 r 防止转义,如 r'C:\data\a.txt',或用正斜杠 /(Windows 也兼容)。
mode(打开模式),模式由三组互斥选项各选其一组成:
| | |
|---|
读写主模式 | | 只读(默认)。文件必须存在,否则报错 FileNotFoundError。 |
| | 只写。文件存在则清空(截断),不存在则新建。危险操作,注意备份! |
| | 独占创建。写模式,但文件已存在则抛出 FileExistsError,防止覆盖。 |
| | 追加。文件存在则末尾追加,不存在则新建。无法读取,只能写。 |
格式模式 | | 文本模式(默认)。读写字符串 str,需指定 encoding。 |
| | 二进制模式。读写字节流 bytes(如图片、音频),禁止传 encoding 参数。 |
附加标识 | | 更新模式(读写兼得)。搭配使用:r+(读写,指针在开头)、w+(清空读写)、a+(追加读写,指针在末尾)。 |
常用组合速查:
仅在 t(文本模式)下有效,b 模式下传此参数会报错。铁律:永远显式指定 encoding='utf-8'。- 因为 Windows 默认编码是
gbk,而 Linux/macOS 是 utf-8。不指定的话,你的代码在别人电脑上大概率 UnicodeDecodeError 乱码报错。
注意事项:
w 模式要当心:它上来就清空文件。如果想在原文件基础上修改,用 r+ 或先读取再写入,千万别直接用 w。seek 移动指针:用 r+ 写入时,内容是从当前指针位置覆盖写入的,不是插入。想追加直接用 a。- 大文件别用
read():读取几 GB 文件时,read() 会把内存撑爆。应用 for line in f: 逐行迭代,内存友好。
4. 读取文件
4.1 read方法
概述:使用文件对象的read方法,读取文件中的内容。其本质是操作文件内部的指针,每次读取都会将指针向后移动。
方法说明:
- 若不传递
size参数,表示:读取文件中所有的内容(内存警告:如果文件有 5GB,这一句直接撑爆内存。仅适用于几 MB 以内的配置文件或文本!)。 - 若传递了
size参数,表示:读取文件中指定个数的字符,或指定大小的字节。
read会从上一次read的位置继续读取,若到达文件末尾后继续读取,将返回空字符串。- 当指针到达文件末尾后继续调用
read,将返回空值(文本模式下为 '',二进制模式下为 b''),这是判断文件是否读完的唯一标志。
# 读取操作:使用『文件对象』的 read 方法,读取文件中的内容。
# read 方法说明:
# 1.read(size)中的 size 是可选参数。
# 若不传递 size 参数,表示:读取文件中所有的内容(注意内存占用!)。
# 若传递了 size 参数,表示:读取文件中指定个数的字符,或指定大小的字节。
# 2.read 会从上一次 read 的位置继续读取(指针思想),若到达文件末尾后继续读取,将返回空字符串。
# region
# 第一步:创建[文件对象]
file = open(file='a.txt', mode='rt', encoding='utf-8')
# file = open('a.txt', 'rt', encoding='utf-8')
# file = open('D:/test/a.txt', 'rt', encoding='utf-8')
# file = open('D:/test/girl.jpg', 'rb')
# 第二步:操作文件(读取)
# 多次调用read去逐步读取文件
# r1 = file.read(2)
# r2 = file.read(3)
# r3 = file.read(4)
# r4 = file.read()
# print(r1, end='')
# print(r2, end='')
# print(r3, end='')
# print(r4, end='')
# 用循环配合多次read(对内存友好)
while True:
result = file.read(10)
if result == '':
break
print(result, end='')
# 第三步:关闭文件
file.close()
# endregion
4.2 readline方法
概述:使用文件对象的readline方法,读取文件中的一行。其底层依赖于换行符(\n)作为行的结束标志,读取时会将换行符一并返回。
方法说明:
readline(size) 中的size是可选参数。- 若不传递
size参数,表示:读取当前行直到遇到换行符 \n 的所有内容(包含末尾的换行符) - 若传递了
size参数,表示:限制当前行最多读取的字符数(文本模式)或字节数(二进制模式)。 - 注意:
size不是行数,而是单行内的截断长度。如果一行的长度超过 size,readline(size) 只会返回该行的前 N 个字符,指针停留在该行中间,下一次调用会继续读取该行的剩余部分,而不会跳到下一行。
readline方法,同样从上一次读取结束的位置继续,当指针到达文件末尾后继续调用,返回空字符串(''),与 read() 的终止逻辑完全一致。
关于换行符的细节(容易被忽略)
关于换行符的细节(容易被忽略):
readline 返回的字符串包含行尾的换行符 \n。因此:- 使用
print(line) 会额外多打印一个空行(因为 print 自带换行)。 - 推荐使用
print(line, end='')(示例中的写法)或 line.rstrip('\n') 来去除换行。 慎用 strip():它会同时去除行首和行尾的空白字符(包括空格、制表符),可能会误删文本首尾的有意义空格。如果你的数据是纯文本且确定不需要首尾空格,用 strip() 没问题;否则请用 rstrip('\n') 只剥离换行符。
# 读取操作:使用文件对象的 readline 方法,读取文件中的一行。
# readline 方法说明:
# 1.readline(size) 中的 size 是可选参数。
# 若不传递 size 参数,表示:读取当前这一行所有的内容。
# 若传递了 size 参数,表示:表示读取当前行时,最多能读取的字符数,或字节数(size不是行数)。
# 2.readline 方法,也是从上一次位置继续读取,若到达文件末尾后继续读取,也是返回空字符串。
# region
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件(读取)
# 依次调用readline逐行读取
# r1 = file.readline()
# r2 = file.readline()
# r3 = file.readline()
# r4 = file.readline()
# print(r1.strip())
# print(r2.strip())
# print(r3.strip())
# print(r4.strip())
# 通过循环配合readline逐行读取
while True:
line = file.readline()
if line == '':
break
# print(line.strip())
print(line, end='')
# 第三步:关闭文件
file.close()
4.3 for 循环遍历文件对象
readline 是处理超大文本文件的利器,配合 while 循环能稳定控制内存峰值,且不受文件总大小影响。如果你只需要逐行遍历,直接用 for line in file: 更优雅,它是 readline 的语法糖封装。
# 读取操作:使用 for 循环直接遍历文件对象
# region
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件(读取)
for line in file:
print(line, end='')
# 第三步:关闭文件
file.close()
# endregion
4.4 readlines方法
概述:使用文件对象的readlines方法,一次性按行读完,返回一个列表。列表中的每个元素,都对应文件中的一行,且每行末尾自带换行符\n。
方法说明:
readlines(hint) 中的hint是可选参数。- 若不传递hint参数,表示:读取当前文件的所有行,将每一行作为列表的一个元素返回。
- 若传递了hint参数,表示:期望读取的
字符个数 或 字节数(hint不是行数,而是字节/字符数的提示)。
- 返回的列表
包含每行末尾的换行符 \n。处理时通常需要用 strip() 或 rstrip('\n') 去掉。 - 注意:由于
readlines是一次性读取文件的所有内容,所以不适合读取体积较大的文件。
# 读取操作:使用文件对象的 readlines 方法,一次性按“行”读完,返回一个列表。
# readlines 方法说明:
# 1.readlines(hint) 中的 hint 是可选参数。
# 若不传递 hint 参数,表示:读取当前文件的所有行。
# 若传递了 hint 参数,表示:期望读取的【字符个数 或 字节数的上限】(hint不是行数)。
# 2.注意:由于 readlines 是一次性读取文件的所有内容,所以不适合读取体积较大的文件。
# region
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件(读取)
result = file.readlines()
print(result)
# 第三步:关闭文件
file.close()
# endregion
总结:readlines 是“全量快照”工具,适合小文件快速取用;readline 和 for line in file 是流水线工具,适合大文件稳吃。数据量未知时,优先选 for line in file:,它是 Python 官方推荐的最安全做法。
四种读取方法使用场景对比表
| | | |
|---|
read() | 小配置文件(几KB)、需要一次性获取全部内容做整体处理的场景(如JSON解析、正则全文匹配) | 全部加载 | |
readline() | 超大日志文件、需要手动控制循环跳出时机的场景,或处理逻辑依赖当前行号的精确控制 | 逐行加载 | |
readlines() | 文件体量中等(几十MB以内),且后续需要通过下标随机访问某一行(如 lines[0]、lines[-1]) | 全部加载为列表,每个字符串对象有额外开销,内存占用比 read() 更高 | |
for line in file: | 99% 的文本逐行遍历场景,无论文件大小,兼顾安全与简洁,是 Python 官方首选 | 底层使用迭代器(惰性加载),内存占与 readline()同等低,代码却更简洁 | |
小文件用read(),大文件无脑上for line in file,需要下标用readlines(),需要手动控制循环用 readline()。
4.5 最佳实践
概述:更推荐使用with上下文管理器,结合for循环遍历,逐行读取文件。
# 最佳实践:使用 with 上下文管理器,结合for循环遍历,逐行读取文件。
with open('a.txt', 'rt', encoding='utf-8') as file:
for line in file:
print(line, end='')
5. 关于with语句的扩展
概述:Python 中的with主要用于管理程序中需要成对出现的操作,例如:
使用with的目标:编码者只管做具体的事,进入和离开的事,让 Python 自动处理。
# 语法格式:
with 能得到一个上下文管理器的表达式 as 变量:
具体的事1
具体的事2
具体的事3
上下文管理器协议:
__enter__ 方法:with中的代码执行之前调用,其返回值会赋值给as后的变量。__exit__ 方法:with中的代码执行结束后调用(无论是with中否出现异常都会调用)。
# 定义一个 Person 类,让其实例对象遵循:上下文管理器协议
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
print(f'我叫{self.name},年龄是{self.age}')
def __enter__(self):
print('-----我是进入的逻辑-----')
return self
# 当 with 中的代码发生异常时,__exit__ 方法的返回值规则如下:
# 返回“真”:表示异常【已经】被处理,异常【不会】被继续抛出。
# 返回“假”:表示异常【没有】被处理,异常【会】被继续抛出。
def __exit__(self, exc_type, exc_val, exc_tb):
print('-----我是离开的逻辑-----')
# exc_type : 异常类型
# exc_val : 异常对象
# exc_tb : 异常追踪信息
if exc_type:
print(f'异常类型:{exc_type}')
print(f'异常对象:{exc_val}')
print(f'异常追踪信息:{exc_tb}')
return True
# 1.计算 with 后面的表达式,得到一个『上下文管理器』。
# 2.调用『上下文管理器』的 __enter__() 方法,并将其返回值赋给 as 后面的变量。
# 3.执行 with 所管理的代码。
# 4.无论代 with 中的代码,是正常结束,还是发生异常,都会自动调用『上下文管理器』的 __exit__ 方法。
with Person('张三', 18) as p1:
p1.speak()
# p1.study()
print(666)