飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

文件读写

程序与外部世界交换数据的最基本途径就是文件。Python 通过内置的 open() 函数打开文件,返回一个文件对象,所有读写操作都围绕这个对象展开。理解文件读写的关键在于掌握打开模式、读写方法和编码处理三个维度。

open() 的打开模式

open(file, mode='r', encoding=None, ...) 的 mode 参数是一个字符串,由若干字符组合而成,决定了文件以何种方式打开。各字符的含义如下:

  • 'r' —— 只读(默认)。文件必须存在,否则抛出 FileNotFoundError。
  • 'w' —— 只写。文件不存在则创建;若已存在,内容会被清空。
  • 'x' —— 独占创建。仅当文件不存在时才创建并打开用于写入,若已存在则抛出 FileExistsError。适合需要避免覆盖的场景。
  • 'a' —— 追加写。文件不存在则创建;若已存在,写入的内容自动附加到末尾。注意,a 模式下 seek() 虽然可以移动指针,但写入始终发生在末尾。
  • 'b' —— 二进制模式。数据以 bytes 对象读写,不经过编码解码。处理图片、视频、可执行文件等必须加 b。
  • 't' —— 文本模式(默认)。读写的是经过编码/解码的 str。
  • '+' —— 读写兼备。必须与 r、w 或 a 组合使用,如 r+、w+、a+。

常用组合及其行为差异:

# r+:读写,文件必须存在,不会清空,指针在开头
with open('data.txt', 'r+', encoding='utf-8') as f:
    f.write('AB')   # 覆盖前两个字符,不会自动清空后面原有内容

# w+:读写,存在则清空,不存在则创建
with open('data.txt', 'w+', encoding='utf-8') as f:
    f.write('hello')
    f.seek(0)
    print(f.read())  # hello

# a+:追加+读,指针初始在末尾;写入总在末尾
with open('data.txt', 'a+', encoding='utf-8') as f:
    f.seek(0)
    print(f.read())  # 可以读到已有内容
    f.write(' world')  # 无论 seek 到哪里,写入仍在末尾

encoding 参数在文本模式下至关重要。省略时,Python 使用平台默认编码(Windows 常为 cp936/gbk,Linux/macOS 常为 utf-8),这会导致跨平台乱码。因此,处理文本文件时始终显式指定 encoding='utf-8'。二进制模式下不能指定 encoding,否则会抛出 ValueError。

文本模式还有一个隐式行为:读取时,平台特定的换行符(Windows 的 \r\n、Unix 的 \n)会被统一转换为 \n;写入时,\n 会被转换回平台特定形式。若需要保留原始换行符,可在文本模式下传入 newline=''。

读取文件内容

文件对象提供多种读取手段,适用不同场景。

read(size=-1) 一次性读取内容。省略 size 或传入负数时,读取整个文件;否则最多读取 size 个字符(文本模式)或字节(二进制模式)。到达文件末尾后,再次调用返回空字符串 '' 或空字节串 b''。

with open('sample.txt', 'r', encoding='utf-8') as f:
    whole = f.read()       # 读取全部
    second = f.read()      # 已到达末尾,返回 ''

readline() 逐行读取,保留行末的 \n。空行表现为 '\n',文件结束表现为 '',二者可据此区分。

with open('sample.txt', 'r', encoding='utf-8') as f:
    line1 = f.readline()   # '第一行\n'
    line2 = f.readline()   # '第二行\n'
    line3 = f.readline()   # ''  ← 文件结束

readlines() 读取剩余所有行,返回列表,每行含换行符。等价于 list(f)。

最高效且最 Pythonic 的方式是直接迭代文件对象,它内部逐行读取,内存占用恒定:

with open('sample.txt', 'r', encoding='utf-8') as f:
    for line in f:
        print(line.strip())   # strip() 去掉首尾空白和换行符

对比四种方式的适用边界:小文件用 read() 简单直接;需要逐行处理且文件巨大时,迭代或 readline() 避免内存爆炸;需要全部行做列表操作时,readlines() 或 list(f) 最方便。

写入文件内容

write(string) 将字符串写入文件,返回写入的字符数(注意不是字节数)。写入后内容不会自动换行,需手动添加 \n。

with open('output.txt', 'w', encoding='utf-8') as f:
    n = f.write('Hello')     # 返回 5
    f.write(' World\n')     # 同一行继续写,最后换行

writelines(lines) 接受一个可迭代对象,将其中每个元素依次写入。不会自动添加换行符,如果元素本身不含 \n,所有内容会挤在一行。

lines = ['第一行\n', '第二行\n', '第三行\n']
with open('output.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines)

# 错误示范:忘记加换行符
bad = ['a', 'b', 'c']
with open('out.txt', 'w', encoding='utf-8') as f:
    f.writelines(bad)   # 文件内容是 'abc',没有分行

文本模式下只能写字符串,若传入数字等对象会抛出 TypeError。二进制模式下只能写字节串:

with open('data.bin', 'wb') as f:
    f.write(b'\x00\x01\x02')
    # f.write('文本')  # TypeError: a bytes-like object is required

文件定位:seek 与 tell

tell() 返回当前文件指针的位置。二进制模式下,这是从文件开头算起的字节数,语义明确。文本模式下,返回的是一个“不透明”的数字,与底层字节偏移不一定对应,因为编码可能导致一个字符对应多个字节。

seek(offset, whence=0) 移动文件指针。whence 的取值:

  • 0(默认):从文件开头计算,offset 必须 >= 0。
  • 1:从当前位置计算,二进制模式专用。
  • 2:从文件末尾计算,二进制模式专用(文本模式下仅 seek(0, 2) 跳到末尾是允许的)。
with open('data.bin', 'rb+') as f:
    f.write(b'0123456789abcdef')
    f.seek(5)              # 跳到第 6 个字节(0-based)
    print(f.read(1))       # b'5'
    f.seek(-3, 2)          # 从末尾回退 3 字节
    print(f.read(1))       # b'd'

文本模式的 seek 受到严格限制:只允许相对于文件开头移动,且 offset 只能是 0 或之前 tell() 返回的值。任意字节偏移会导致未定义行为,因为多字节编码的字符可能被截断。

with open('text.txt', 'r', encoding='utf-8') as f:
    f.read(2)
    pos = f.tell()
    f.seek(pos)            # 安全,回到刚才的位置
    # f.seek(1)            # 危险!可能落在某个 UTF-8 字符的中间

with 语句与资源安全

文件对象占用操作系统资源,用完后必须关闭以释放句柄并确保缓冲区数据落盘。with 语句通过上下文管理器协议保证文件在代码块结束时自动关闭,即使发生异常也不例外。

# 不推荐:容易遗漏 close(),且异常时不会关闭
f = open('data.txt', 'r', encoding='utf-8')
data = f.read()
f.close()

# 推荐:简洁且安全
with open('data.txt', 'r', encoding='utf-8') as f:
    data = f.read()
# 离开 with 块后,f.closed 为 True

未关闭的文件可能导致数据丢失:写入操作通常先进入内存缓冲区,close() 或刷新才会真正写入磁盘。程序正常退出时 Python 会关闭剩余文件,但依赖这一行为是危险的,因为异常退出或长时间运行的程序会累积资源泄漏。

关闭后的文件对象不可再用,否则抛出 ValueError:

f = open('tmp.txt', 'w', encoding='utf-8')
f.close()
f.read()   # ValueError: I/O operation on closed file.

编码问题深度处理

当读取未知来源的文件时,若编码指定错误,会抛出 UnicodeDecodeError。此时可用 errors 参数控制解码失败行为:

  • 'strict'(默认):抛出异常。
  • 'ignore':静默丢弃无法解码的字符。
  • 'replace':用 \ufffd()替换。
  • 'backslashreplace':用 \xNN 转义表示。
# 文件实际是 gbk 编码,但按 utf-8 读取会报错
with open('legacy.txt', 'r', encoding='utf-8', errors='replace') as f:
    text = f.read()   # 乱码处显示为,至少不会崩溃

# 若已知是 gbk,直接指定正确编码
with open('legacy.txt', 'r', encoding='gbk') as f:
    text = f.read()

写入时同样可指定 errors,处理无法编码的字符。对于混合编码数据的批量处理,建议先用 chardet 等库检测编码,再按检测结果打开。

总结

open() 的模式字符自由组合,决定了文件的读写权限、是否存在、是否清空以及文本/二进制形态。读取有 read、readline、readlines 和迭代四种粒度;写入有 write 和 writelines,均不自动换行。seek 和 tell 在二进制模式下最可靠,文本模式受限。始终使用 with 语句管理文件生命周期,并显式指定 encoding='utf-8' 以避免跨平台编码陷阱。

下一页
上下文管理器