文件读写

作为 Python 日常开发中最常见的 IO 场景之一,文件读写看似简单,细节却能决定代码的健壮性、效率甚至安全性——今天咱们就用一篇短文,把它讲明白,从基础 API 到避坑指南全搞定!

文件操作基础

Python 使用内置的 open() 函数完成核心的文件操作,它会返回一个文件对象/文件描述符——这就像连接 Python 代码和操作系统磁盘文件的“桥梁”。

现代操作系统出于安全与稳定性考虑,禁止程序直接读写磁盘扇区,所有文件操作都必须通过系统提供的统一 API 包装。这也是为什么我们不能简单用内存地址来读取文件的原因:必须让操作系统替我们完成实际的 I/O 工作。


读取文件

基础打开与exception-handling

open() 读取文件时,默认模式为 'r'(文本读取)。但如果你给的路径不存在,Python 会毫不留情地抛出 FileNotFoundError 打断程序:

# 基础读取打开
f = open('/path/to/your/file.txt', 'r')

为了让程序更稳健,务必加上最基本的异常捕获:

try:
    f = open('/path/to/nonexistent.txt', 'r')
except FileNotFoundError as e:
    print(f"文件不存在,请检查路径:{e}")

四种常用读取内容的方式

成功打开文件后,根据文件大小、读取需求,选择对应的方法:

  1. 一次性全读 – 适合小文件(通常几十 KB 以内)

    content = f.read()  # 读取整个文件到单个字符串
  2. 按字节/字符分段读 – 大文件友好,避免一次性加载占满内存

    chunk = f.read(1024)  # 文本模式读1024个字符,二进制模式读1024字节
  3. 逐行单读 – 需要根据每行逻辑处理时常用

    line = f.readline()  # 读取一行,包含末尾的换行符 \n
  4. 全读进列表 – 后续要多次操作行内容时很方便

    lines = f.readlines()  # 每一行(含 \n)作为列表的一个元素

安全释放资源的两种方法

文件打开后会占用操作系统的文件描述符资源(每个进程可用的数量有限,比如 Linux 默认通常是 1024 个),用完必须及时关闭!

  1. 手动关闭 – 容易忘,而且在读写抛出异常时会跳过 close()

    f = open('/path/to/file.txt', 'r')
    content = f.read()
    f.close()  # 只有放在 try/except/finally 里才真正安全
  2. with 自动管理(强烈推荐) – 利用上下文管理器,代码块结束时自动触发 close(),即使中途发生异常也能正确关闭

    with open('/path/to/file.txt', 'r') as f:
        content = f.read()
    # 此处文件已经自动关闭,f 变量不再可用

文件对象类型

Python 支持三类核心的“文件-like”对象,搞清楚它们的使用场景能让你少走很多弯路:

  1. 磁盘文本文件
    默认打开方式,按字符/字符串处理,会自动进行解码和编码。

    with open('note.txt', 'rt') as f:  # 't' 可省略
        pass
  2. 磁盘二进制文件
    处理图片、视频、压缩包等非文本内容,按字节/字节串处理。

    with open('cat.jpg', 'rb') as f:
        img_bytes = f.read()
  3. 内存文件
    完全在内存中模拟文件操作,无需真实磁盘 I/O,速度极快,非常适合处理临时数据。

    from io import StringIO, BytesIO
    
    # 文本内存流
    text_stream = StringIO("临时的博客草稿")
    text_stream.write("\n再加一行修改")
    print(text_stream.getvalue())  # 不用 with 也能直接取所有内容
    text_stream.close()
    
    # 二进制内存流
    binary_stream = BytesIO(b'\x48\x65\x6c\x6c\x6f')  # 对应 "Hello"

处理不同编码的文本文件

Python 3 默认使用 UTF-8 编码读写文本,但在 Windows 上经常碰到 GBK / GB2312 的旧文件,直接打开会抛出 UnicodeDecodeError

# 指定编码读取 GBK 文件
with open('old_chinese_note.txt', 'r', encoding='gbk') as f:
    content = f.read()

如果文件中存在少量非法字符(如混杂的乱码),可以用 errors 参数调整处理策略:

  • errors='ignore' – 忽略非法字符
  • errors='replace' – 用 替换非法字符
  • errors='strict' – 默认值,直接报错(最安全,提醒你文件有问题)
# 遇到乱码就替换
with open('mixed_encoding.txt', 'r', encoding='utf-8', errors='replace') as f:
    content = f.read()

写入文件

两种基础写入模式

写入操作最关键的是防止误覆盖原有数据,先来看最常用的两种模式:

  1. 覆盖写入('w'
    文件不存在时会自动创建;文件已存在时,打开瞬间清空所有内容然后从头开始写。

    with open('new_diary.txt', 'w', encoding='utf-8') as f:
        f.write("今天学了 Python 文件读写!")
  2. 追加写入('a'
    文件不存在自动创建;已存在则从文件末尾开始写,保留原有内容。

    with open('new_diary.txt', 'a', encoding='utf-8') as f:
        f.write("\n明天继续学 CSV/JSON 读写!")

写入多行的两种方式

可以选择循环调用 f.write(),也可以用更简洁的 f.writelines()

lines = ["第一行", "第二行", "第三行"]

# 方式1:手动加换行符循环写
with open('multi_line.txt', 'w', encoding='utf-8') as f:
    for line in lines:
        f.write(f"{line}\n")

# 方式2:用生成器表达式批量传值(更 Pythonic)
with open('multi_line.txt', 'w', encoding='utf-8') as f:
    f.writelines(f"{line}\n" for line in lines)

⚠️ 注意f.writelines() 不会自动添加换行符!你必须自己手动拼接 \n


文件模式速查表

基础模式核心功能组合后缀补充说明
'r'只读(默认)'t'文本模式(默认,可省略)
'w'只写(清空覆盖,自动创建)'b'二进制模式
'x'独占创建(文件存在则报错)'+'可同时读写(基础模式功能保留)
'a'只写(末尾追加,自动创建)

常见组合示例:

  • 'rb' – 读取图片、压缩包等二进制文件
  • 'w+' – 读写(覆盖式,读完后需手动移动文件指针才能重读)
  • 'a+' – 读写(追加式,读完同样要移动指针)

避坑与最佳实践

  1. 永远默认加上 encoding='utf-8'
    避免跨平台(Windows ↔ Linux / macOS)时出现编码问题。
  2. 永远使用 with 语句
    忘记手动关闭文件是新手最容易犯的低级错误,用 with 一劳永逸。
  3. 大文件必须逐行/分段迭代
    比如处理 GB 级的日志文件,用 for line in f: 逐行读比 f.readlines() 内存友好 100 倍:
    with open('10GB_server.log', 'r', encoding='utf-8') as f:
        for line in f:  # 逐行迭代,内存占用极小
            if "ERROR" in line:
                print(line.strip())
  4. 'x' 模式避免覆盖关键数据
    比如写用户配置文件时,防止不小心把旧配置删了:
    try:
        with open('user_config.json', 'x', encoding='utf-8') as f:
            f.write('{"theme": "dark"}')
    except FileExistsError:
        print("配置文件已存在,无需创建")

快速练习:读取系统时区文件

用刚学的知识写个小脚本,读取 Linux / macOS 常用的时区文件:

import os

TIMEZONE_PATH = '/etc/timezone'

if __name__ == '__main__':
    if not os.path.exists(TIMEZONE_PATH):
        print("当前系统没有找到 /etc/timezone 文件(可能是 Windows)")
        exit(1)
    
    try:
        with open(TIMEZONE_PATH, 'r', encoding='utf-8') as f:
            print(f"当前系统时区:{f.read().strip()}")  # strip() 去掉末尾换行
    except PermissionError:
        print("没有权限读取 /etc/timezone 文件,请用 sudo 运行")

总结

今天我们梳理了 Python 文件读写的核心知识点:

  1. 使用 open() 配合 with 上下文管理器安全地打开与关闭文件
  2. 区分文本、二进制、内存三种文件对象的使用场景
  3. 处理编码问题(encodingerrors 参数)
  4. 覆盖、追加、独占创建三种基础写入模式
  5. 大文件处理的内存友好技巧

掌握这些,日常开发中 90% 的文件 I/O 需求都能轻松搞定!下一篇我们一起聊聊更高级的 CSV / JSON / Excel 结构化文件读写~