第 9 章 文件与 I/O 操作
9.1 文件打开模式
open() 是 Python 中操作文件的核心函数:
f = open("example.txt", mode="r", encoding="utf-8")
content = f.read()
f.close() # 别忘了关闭!
| 模式 | 说明 | 文件不存在时 |
|---|---|---|
r | 只读(默认) | 报错 |
w | 写入(清空原内容) | 创建 |
a | 追加 | 创建 |
x | 排他创建(文件已存在则报错) | 创建 |
b | 二进制模式(与上面组合:rb, wb) | — |
t | 文本模式(默认,与上面组合:rt, wt) | — |
+ | 读写模式(r+, w+, a+) | — |
# 常见组合
open("f.txt", "r") # 读取文本(默认)
open("f.txt", "w") # 写入文本(覆盖)
open("f.txt", "a") # 追加文本
open("f.txt", "r+") # 读写文本
open("f.bin", "rb") # 读取二进制
open("f.bin", "wb") # 写入二进制
open("f.txt", "x") # 创建新文件(已存在则报错)
9.2 上下文管理器与 with 语句
with 语句确保文件自动关闭,即使发生异常:
# 推荐写法
with open("example.txt", "r", encoding="utf-8") as f:
content = f.read()
# 离开 with 块后,文件自动关闭
# 等价于
f = open("example.txt", "r", encoding="utf-8")
try:
content = f.read()
finally:
f.close()
# 同时打开多个文件
with open("input.txt") as fin, open("output.txt", "w") as fout:
for line in fin:
fout.write(line.upper())
# Python 3.10+ 可以用括号换行
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
):
pass
9.3 文本文件读写与编码
读取
# 一次性读取全部内容
with open("data.txt", encoding="utf-8") as f:
content = f.read() # 字符串
print(len(content))
# 按行读取为列表
with open("data.txt", encoding="utf-8") as f:
lines = f.readlines() # 列表,每行含 \n
lines = [line.rstrip("\n") for line in lines]
# 逐行读取(最省内存)
with open("data.txt", encoding="utf-8") as f:
for line in f: # 文件对象本身就是迭代器
line = line.rstrip("\n")
print(line)
# 读取指定字节数
with open("data.txt", encoding="utf-8") as f:
chunk = f.read(100) # 读 100 个字符
rest = f.read() # 读取剩余所有
写入
# 写入(覆盖)
with open("output.txt", "w", encoding="utf-8") as f:
f.write("第一行\n")
f.write("第二行\n")
# 写入多行
lines = ["line 1", "line 2", "line 3"]
with open("output.txt", "w", encoding="utf-8") as f:
f.writelines(line + "\n" for line in lines)
# 追加
with open("log.txt", "a", encoding="utf-8") as f:
f.write("新的日志条目\n")
# print 也能写入文件
with open("output.txt", "w", encoding="utf-8") as f:
print("Hello, World!", file=f)
print("Second line", file=f)
编码
# 指定编码(强烈推荐总是显式指定)
with open("chinese.txt", "w", encoding="utf-8") as f:
f.write("你好世界")
# 读取 GBK 编码的文件
with open("old_file.txt", "r", encoding="gbk") as f:
content = f.read()
# 处理编码错误
with open("mixed.txt", "r", encoding="utf-8", errors="ignore") as f:
content = f.read() # 忽略无法解码的字节
with open("mixed.txt", "r", encoding="utf-8", errors="replace") as f:
content = f.read() # 用 ? 替换无法解码的字节
# 转码:GBK → UTF-8
with open("gbk_file.txt", "r", encoding="gbk") as fin:
content = fin.read()
with open("utf8_file.txt", "w", encoding="utf-8") as fout:
fout.write(content)
9.4 二进制文件读写
# 读取二进制文件
with open("image.png", "rb") as f:
data = f.read() # bytes 对象
print(type(data)) # <class 'bytes'>
print(data[:8]) # PNG 文件头
# 写入二进制文件
with open("copy.png", "wb") as f:
f.write(data)
# 分块读取大文件
def copy_file(src, dst, chunk_size=8192):
with open(src, "rb") as fin, open(dst, "wb") as fout:
while chunk := fin.read(chunk_size):
fout.write(chunk)
# 文件指针操作
with open("data.bin", "rb") as f:
f.seek(10) # 移动到第 10 个字节
data = f.read(4) # 读 4 个字节
pos = f.tell() # 当前位置:14
f.seek(0) # 回到开头
f.seek(-10, 2) # 从末尾倒数 10 字节(2 表示从末尾算)
9.5 pathlib 模块:面向对象的路径操作
pathlib 是 Python 3.4+ 引入的现代路径处理方式,比 os.path 更优雅:
from pathlib import Path
# 创建路径
p = Path("data/files/report.txt")
home = Path.home() # 用户主目录
cwd = Path.cwd() # 当前工作目录
# 路径拼接(用 / 运算符)
config = Path.home() / ".config" / "myapp" / "settings.json"
# 路径属性
p = Path("/home/user/data/report.csv")
p.name # "report.csv"
p.stem # "report"
p.suffix # ".csv"
p.suffixes # [".csv"](多后缀如 .tar.gz → ['.tar', '.gz'])
p.parent # Path("/home/user/data")
p.parents[1] # Path("/home/user")
p.parts # ('/', 'home', 'user', 'data', 'report.csv')
p.is_absolute() # True
文件操作
# 读写文件(简化版)
p = Path("example.txt")
p.write_text("Hello, World!", encoding="utf-8")
content = p.read_text(encoding="utf-8")
p = Path("data.bin")
p.write_bytes(b"\x00\x01\x02")
data = p.read_bytes()
# 判断
p.exists() # 是否存在
p.is_file() # 是否是文件
p.is_dir() # 是否是目录
p.is_symlink() # 是否是符号链接
# 文件信息
stat = p.stat()
stat.st_size # 文件大小(字节)
stat.st_mtime # 修改时间(时间戳)
目录操作
d = Path("project")
# 创建目录
d.mkdir(exist_ok=True) # 单层
d.mkdir(parents=True, exist_ok=True) # 递归创建
# 遍历目录
for item in Path(".").iterdir():
print(item.name, "目录" if item.is_dir() else "文件")
# glob 模式匹配
for py_file in Path("src").glob("*.py"):
print(py_file)
for py_file in Path("src").rglob("*.py"): # 递归搜索
print(py_file)
# 实用示例:查找项目中所有 Python 文件并统计行数
total_lines = 0
for py_file in Path(".").rglob("*.py"):
lines = py_file.read_text(encoding="utf-8").splitlines()
total_lines += len(lines)
print(f"{py_file}: {len(lines)} 行")
print(f"总计: {total_lines} 行")
路径操作
p = Path("data/raw/report.csv")
# 修改文件名
p.with_name("summary.csv") # Path("data/raw/summary.csv")
p.with_stem("summary") # Path("data/raw/summary.csv") (3.9+)
p.with_suffix(".txt") # Path("data/raw/report.txt")
# 解析为绝对路径
p.resolve() # Path("/abs/path/data/raw/report.csv")
# 相对路径
p.relative_to("data") # Path("raw/report.csv")
# 删除
p.unlink(missing_ok=True) # 删除文件
d.rmdir() # 删除空目录
9.6 序列化:json、pickle、shelve
json — 通用数据交换格式
import json
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding"],
"address": {"city": "Beijing", "zip": "100000"}
}
# 对象 → JSON 字符串
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# JSON 字符串 → 对象
parsed = json.loads(json_str)
# 写入文件
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 从文件读取
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
JSON 类型映射:
| Python | JSON |
|---|---|
| dict | object |
| list, tuple | array |
| str | string |
| int, float | number |
| True/False | true/false |
| None | null |
自定义序列化:
from datetime import datetime
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
data = {"created": datetime.now()}
json.dumps(data, cls=DateEncoder)
# '{"created": "2026-03-31T10:30:00.123456"}'
pickle — Python 对象序列化
import pickle
data = {"name": "Alice", "scores": [90, 85, 92]}
# 序列化
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
# 反序列化
with open("data.pkl", "rb") as f:
loaded = pickle.load(f)
# pickle 可以序列化几乎任何 Python 对象(函数、类等)
# 但注意:不要 unpickle 不受信任的数据(安全风险)
shelve — 简易的持久化字典
import shelve
with shelve.open("mydata") as db:
db["users"] = ["Alice", "Bob"]
db["config"] = {"theme": "dark"}
with shelve.open("mydata") as db:
print(db["users"]) # ['Alice', 'Bob']
print(list(db.keys())) # ['users', 'config']
9.7 StringIO 与 BytesIO
在内存中创建文件对象,用于需要文件接口但不想写磁盘的场景:
from io import StringIO, BytesIO
# StringIO — 内存中的文本文件
sio = StringIO()
sio.write("Hello, ")
sio.write("World!")
content = sio.getvalue() # "Hello, World!"
# 作为输入源
sio = StringIO("line1\nline2\nline3")
for line in sio:
print(line.strip())
# BytesIO — 内存中的二进制文件
bio = BytesIO()
bio.write(b"binary data")
data = bio.getvalue()
# 实用:CSV 处理不落盘
import csv
output = StringIO()
writer = csv.writer(output)
writer.writerow(["name", "age"])
writer.writerow(["Alice", 30])
csv_content = output.getvalue()
print(csv_content)
# name,age
# Alice,30
本章小结:文件 I/O 是编程的基础技能。始终使用
with语句管理文件、始终显式指定编码、用pathlib替代os.path处理路径、根据需求选择 json/pickle/shelve 进行序列化。掌握这些,你就能自如地处理各种文件操作需求。