Skip to main content

文件操作

info

文件操作是程序与外部数据交互的基础。Python 提供了简洁且功能强大的内置函数和模块,用于处理各类文件。无论是读写文本、处理二进制数据,还是管理文件生命周期,Python 的设计都注重易用性和安全性。

  • open() 函数是文件操作的核心入口
  • with 语句是处理文件的最佳实践
  • 理解文本模式与二进制模式的区别至关重要

文件

在正式读写文件之前,先把几个高频概念说清楚,这会帮你避开大量「看起来很玄学」的 Bug。

  • 文件名与扩展名

    • 一个完整文件名通常由「主文件名 + 扩展名」组成,如 report.pdfdata.csv
    • 扩展名只是约定俗成的类型提示,操作系统通常不会强制校验;真正决定内容的是文件内部的格式规范。
    • 主文件名和扩展名都可以省略,例如:.gitignore.envDockerfileLICENSE
  • 路径:绝对路径与相对路径

    • 绝对路径:从磁盘根目录开始描述文件位置,例如 C:\Users\jiang\Desktop\file.txt/home/user/file.txt
    • 相对路径:以当前工作目录?为参照,如 ./data/file.txt../logs/app.log
    • 跨平台时要注意路径分隔符差异(Windows 常见 \,Linux/macOS 使用 /),推荐使用 pathlib 统一处理。
  • 文件类型:文本文件与二进制文件

    • 文本文件:内容是按某种字符编码保存的文本,例如 .txt.md.py。 通过记事本可正常打开的都属于文本文件。
    • 二进制文件:内容是原始字节流,例如图片、音频、视频、数据库、可执行程序等需要专门软件打开的文件。
    • 在 Python 中,主要区别体现在打开模式:'t'(文本模式,默认)与 'b'(二进制模式),以及是否需要编码/解码。
  • 编码格式(encoding)

    • 文本文件在落盘时必须选择一种字符编码(如 UTF-8GBKISO-8859-1 等)。
    • 读取文本时,Python 会按照你提供的编码参数(或系统默认编码)把字节解码为 str;编码不匹配时就会出现“乱码”或 UnicodeDecodeError
    • 建议统一使用 UTF-8,并在 open() 时显式指定 encoding='utf-8',避免不同平台默认编码不一致的问题。
  • 换行符与跨平台差异

    • Unix 系统使用 \n 作为换行符,Windows 传统上使用 \r\n
    • 文本模式下,Python 会自动进行换行符转换(具体行为受 newline 参数控制),二进制模式则保持字节原样,不做任何转换。
    • 如果你在不同系统之间传文件、做文本对比工具,最好了解这一点,以免出现“明明一样却行数不对”的困惑。
  • 文件元数据与权限(了解即可)

    • 每个文件除了内容本身,还有大小、创建/修改时间、权限、拥有者等元数据,可通过 os.stat()pathlib.Path.stat() 获取。
    • 在类 Unix 系统上,读写权限会直接影响 Python 能否打开文件;在 Windows 上也会涉及文件占用、锁等问题。

open 函数

open() 函数是 Python 内置的,用于打开文件并返回对应的文件对象。

函数签名:open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

  • file

    • 一个 path-like object,表示将要打开的文件的路径(绝对路径或相对当前工作目录的路径)。
    • 也可以是要封装文件对应的整数类型文件描述符(如果给出的是文件描述符,则当返回的 I/O 对象关闭时它也会关闭,除非将 closefd 设为 False)。
  • mode

    • 一个指明文件打开模式的可选字符串,默认为 'r' (以文本模式读取)。
    • 详细模式字符及含义见下表。参数 mode 可以叠加使用,例如 rb 表示以二进制模式读取并写入。
    • 'r':读取(默认)
    • 'w':写入,并先截断文件(清空)
    • 'x':排它性创建,如果文件已存在则失败
    • 'a':打开文件用于写入,如果文件存在则在末尾追加
    • 'b':二进制模式
    • 't':文本模式(默认)
    • '+':打开用于更新(读取与写入)
  • buffering

    • 一个可选的整数,用于设置缓冲策略。
    • 0:关闭缓冲(仅在二进制模式下允许)。
    • 1:行缓冲(仅在文本模式下写入时可用)。
    • >1:表示固定大小的块缓冲区的字节大小。
    • 默认行为:二进制文件通常使用 128KB 块缓冲;交互式文本文件使用行缓冲;其他文本文件使用与二进制文件相同的块缓冲策略。
  • encoding

    • 用于编码或解码文件的编码格式名称,仅在文本模式下使用。
    • 默认编码格式依赖于具体平台(locale.getencoding())。
  • errors

    • 可选字符串参数,指定如何处理编码和解码错误,不能在二进制模式下使用。
    • 'strict' (默认,引发 ValueError)
    • 'ignore':忽略错误,继续执行。
    • 'replace':替换为 ?
    • 'surrogateescape':使用代理字符。
    • 'xmlcharrefreplace':替换为 XML 字符引用。
    • 'backslashreplace':替换为反斜杠。
    • 'namereplace':替换为名称。
  • newline

    • 决定如何解析来自流的换行符。可为 None, '', '\n', '\r''\r\n'
    • 读取时:None 启用通用换行模式并转换为 '\n''' 启用通用换行模式但不转换;其他值则按给定字符串终止行且不转换。
    • 写入时:None 转换为系统默认行分隔符 os.linesep'''\n' 不进行翻译;其他值转换为给定字符串。
  • closefd

    • 如果为 Falsefile 是文件描述符,则当文件对象关闭时,底层文件描述符保持打开状态。如果 file 是文件名,则 closefd 必须为 True(默认值)。
  • opener

    • 一个可调用的自定义开启器。通过 opener(file, flags) 获取文件对象的基础文件描述符。

读写文本文件

使用 'w' 模式(写入并清空)、'a' 模式(追加写入)或 'w+' (读写并清空)模式打开文件进行写入。如果文件不存在,这些模式会自动创建文件。

# 使用 'w' 模式写入,如果文件不存在则创建,存在则清空
f = open('test.txt', 'w')
f.write('hello world.')
f.close()

print(open('test.txt').read()) # 输出:hello world.

# 使用 'a' 模式追加写入
f = open('test.txt', 'a')
f.write(' appended content.')
f.close()

print(open('test.txt').read()) # 输出:hello world. appended content.

# 使用 'w+' 模式读写(会先清空文件),并使用 seek 定位
f = open('test.txt', 'w+')
f.write('hello world. morning.')
f.seek(3) # 将文件指针移动到第3个字节
print(f.read()) # 输出:lo world. morning.
f.close()

使用 'r' 模式(默认)或 'r+' (读写但不清空)模式打开文件进行读取。如果文件不存在,会引发 FileNotFoundError

# 默认以 'r' 模式打开文件,读取所有内容
f = open("test.txt")
text = f.read()
print(text)
f.close()

# 按照行读入内容,readlines 方法返回一个列表
f = open("test.txt")
lines = f.readlines()
print(lines) # 输出:['hello world. morning.
']
f.close()

# 迭代文件对象,逐行读取内容
f = open('test.txt')
for line in f:
print(line, end='') # end='' 防止额外换行
f.close()

读写二进制文件

当需要处理图片、音频、视频等非文本数据时,应以二进制模式('b')打开文件。

在二进制模式下,所有数据都以 bytes 对象形式读写,不进行任何编码或解码。

import os

# 以 'wb' 模式写入 10 个随机字节到二进制文件
f = open('binary.bin', 'wb')
f.write(os.urandom(10))
f.close()

# 以 'rb' 模式读取二进制文件内容
f = open('binary.bin', 'rb')
print(repr(f.read())) # 输出:b'...' (bytes representation)
f.close()

with 语句

with 语句是 Python 中处理文件(及其他需要资源清理的操作)的最佳实践。它确保文件在使用完毕后,无论是否发生异常,都会被正确关闭。

with 语句的优势

with 语句通过上下文管理器协议 (__enter____exit__ 方法) 自动管理资源的获取和释放。它替代了传统的 try...finally 模式,使代码更简洁、更安全,有效避免了资源泄漏。

这是打开文件的推荐方式。

# 使用 with 语句,文件会在代码块结束时自动关闭
with open('my_file.txt', 'w') as fp:
fp.write("Hello world")

# 上述代码等效于以下 try...finally 结构,但更简洁安全:
fp = open('my_file.txt', 'w')
try:
fp.write("Hello world")
finally:
fp.close()

内置函数

memoryview函数

memoryview 函数主要用于需要高性能和低内存开销的场景,特别是在处理大型二进制数据时。它提供了一个“视图”来直接、高效地访问和操作底层对象的内存,而不需要为数据创建昂贵的副本。

考虑一个场景,你需要从二进制文件中读取大量结构化的记录(例如,每条记录包含一个整数和一个浮点数),并且可能需要修改它们。

传统低效方式每次读取和解包一条记录都会创建新的 bytes 对象,效率低下。

memoryview 允许我们一次性读取一个较大的块,然后通过“视图”来遍历和修改这块内存,避免不必要的数据拷贝。

import struct
import array # 用于创建可变缓冲区

# 假设 data.bin 文件已经存在并有足够的数据
# 我们可以先创建一个模拟的 data.bin 文件
with open("data.bin", "wb") as f:
for i in range(1000):
# 写入一个 int32 (i) 和一个 double64 (i * 1.5)
f.write(struct.pack('<id', i, i * 1.5))

# 实际应用:读取并处理文件
record_size = 12 # 4字节 (int) + 8字节 (double)
num_records_to_read = 100

with open("data.bin", "rb") as f:
# 一次性读取100条记录的字节数据到内存
buffer = f.read(num_records_to_read * record_size)

# 将 buffer 转换为一个可变的 bytearray,以便我们能通过 memoryview 修改它
byte_data = bytearray(buffer)

# 创建一个 memoryview 对象,指向这个 byte_data
# 使用 struct 模块的格式字符串来指定 memoryview 的格式
# 这使得我们可以像访问数组一样访问数据,但背后是 memoryview
structured_mv = memoryview(byte_data).cast('id', shape=[num_records_to_read])

# 现在我们可以像访问数组一样,高效地访问和修改数据
for i in range(num_records_to_read):
record_id, value = structured_mv[i]
# print(f"Record {i}: ID={record_id}, Value={value}")

# 如果需要,我们可以直接修改内存中的值
structured_mv[i] = (record_id, value + 10.0)

# 现在,如果我们将 byte_data 写回文件,修改就已经生效了
# 或者在内存中处理完后,将整个 byte_data 对象传递给其他函数,而不用担心数据拷贝

# 示例:验证修改后的数据
with open("data.bin", "rb") as f:
f.seek(0) # 回到文件开头
modified_buffer = f.read(num_records_to_read * record_size)
modified_byte_data = bytearray(modified_buffer)
modified_structured_mv = memoryview(modified_byte_data).cast('id', shape=[num_records_to_read])

# for i in range(5): # 仅打印前5条验证
# record_id, value = modified_structured_mv[i]
# print(f"Modified Record {i}: ID={record_id}, Value={value}")

标准库推荐

  • os: 操作系统接口,提供文件路径、目录遍历、进程相关等功能,是进行文件/目录管理的基础模块。
  • io: 提供更底层、灵活的流接口(文本流、二进制流等)。
  • time: 提供时间相关函数(sleep、时间戳获取、格式化等),常与文件时间戳、日志记录配合使用。
  • ctypes: 允许调用 C 动态库,适合在需要底层系统调用或高性能 I/O 操作时使用。
  • logging: 标准日志系统,支持将日志输出到文件、控制台、网络等多种目标,便于调试与运维。
  • glob: 使用通配符(如 *.txt)匹配文件路径,适合批量处理一类文件。
  • pathlib: 面向对象的路径操作库,统一文件路径的拼接、遍历、读写等操作,推荐替代 os.path
  • tempfile: 安全创建临时文件和临时目录,避免命名冲突和安全风险。
  • pickle: 将 Python 对象序列化到二进制文件或从中反序列化,适合快速持久化内部数据结构(不适合不可信输入)。
  • sqlite3: 内置轻量级数据库引擎,将结构化数据存入本地 .db 文件,适合桌面应用与小型项目。
  • compression: 各类压缩格式相关模块的统称,可结合 gzipbz2lzma 等进行压缩/解压。
  • tarfile: 读写 .tar.tar.gz 等归档文件,适合打包/解包一组文件。
  • zipfile: 读写 .zip 压缩包,支持添加、解压、遍历压缩包内的文件。
  • csv: 读写 CSV 文本文件,支持方言配置、逐行解析等,常用于数据导入导出。
  • tomllib: 解析 TOML 配置文件(从 Python 3.11 起内置),适合读取项目或工具的配置。
  • struct: 处理二进制数据与 C 结构体打包/解包,非常适合自定义二进制文件格式解析。
  • base64: Base16、Base32、Base64、Base85数据编码。
  • json: JSON编码器和解码器。
  • html: 超文本标记语言支持及其解析器和实体。
  • xml: XML处理模块,包括ElementTree、DOM、SAX等。
  • colorsys: 在 RGB、HSV 等颜色空间之间进行转换,可用于图像或可视化数据的颜色处理。
  • wave: 读写 .wav 音频文件,适合简单的音频数据处理与分析。