🎯 学习目标
- 掌握
open()的各种模式与文本文件读写 - 理解为什么要用
with,了解上下文管理器原理 - 掌握
read/readline/readlines与按行遍历大文件 - 熟练使用现代路径库
pathlib与目录操作 - 掌握 JSON、CSV 两大数据格式的读写(NLP 语料最常用)
- 了解 pickle 的用法与安全风险
1open 与文件打开模式
读写文件三步走:打开 → 操作 → 关闭。内置函数 open(路径, 模式, encoding=...) 返回文件对象。
| 模式 | 名称 | 文件不存在时 | 说明 |
|---|---|---|---|
"r" | 只读(默认) | 报错 FileNotFoundError | 最常用 |
"w" | 只写 | 新建 | 覆盖已有全部内容(小心!) |
"a" | 追加 | 新建 | 在文件末尾追加,不覆盖 |
"r+" | 读写 | 报错 | 不新建 |
b | 二进制 | "rb"/"wb"/"ab":读写图片、模型等,不带 encoding | |
t | 文本(默认) | "rt"/"wt":读写 str,按编码转换 | |
# 最原始的写法(不推荐,忘记 close 会占用资源)
f = open("demo.txt", "w", encoding="utf-8")
f.write("你好")
f.close()
# ✅ 推荐:with 语句,结束后自动关闭(哪怕中途异常也会关)
with open("demo.txt", "w", encoding="utf-8") as f:
f.write("第一行\n")
f.write("第二行\n")
Windows 处理中文文件,encoding="utf-8" 几乎必须写(否则默认 GBK,读别人写的 UTF-8 文件就乱码/报错)。二进制模式 "rb"/"wb" 处理的是 bytes,不能指定 encoding。
2读取文本文件
# 一次性读取整个文件为字符串
with open("demo.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 按行读取
with open("demo.txt", encoding="utf-8") as f:
first = f.readline() # 读一行(含末尾 \n)
rest = f.readlines() # 剩下所有行 → 列表 ['第二行\n']
print(repr(first))
# ✅ 最 Pythonic:文件对象本身可迭代,逐行读取省内存(大文件首选)
with open("big.txt", encoding="utf-8") as f:
for line_no, line in enumerate(f, 1):
line = line.strip() # 去掉行尾换行和空白
if line: # 跳过空行
print(line_no, line)
几个 G 的日志/语料文件千万不要 read(),内存会爆。用 for line in f 逐行处理,内存占用恒定。
3写入文件
# write:写入字符串,返回字符数;不会自动加换行
lines = ["Python", "NLP", "jieba"]
with open("langs.txt", "w", encoding="utf-8") as f:
for item in lines:
f.write(item + "\n")
# writelines:写入字符串序列(注意:也不会自动加换行!)
with open("langs.txt", "w", encoding="utf-8") as f:
f.writelines(x + "\n" for x in lines)
# 追加模式 a:日志场景
with open("run.log", "a", encoding="utf-8") as f:
f.write("2026-10-25 程序启动\n")
4with 与上下文管理器原理
with 之所以能自动关闭,是因为文件对象实现了上下文管理协议(__enter__/__exit__)。等价于下面的 try/finally:
# with open(...) as f: ... 等价于:
f = open("demo.txt", encoding="utf-8")
try:
content = f.read()
finally:
f.close() # 无论是否异常都保证关闭
# 自定义上下文管理器(了解)
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
print(f"耗时 {time.time() - self.start:.4f} 秒")
return False # 返回 False:异常继续向外抛(不吞异常)
with Timer():
total = sum(range(1_000_000))
更简单的方式是用 contextlib 写生成器版上下文管理器,第 12、14 章会再见到。
5路径处理:pathlib 现代写法
老式 os.path 是一堆字符串函数,Python 3.4+ 推荐用面向对象的 pathlib,跨平台、更直观。
from pathlib import Path
# 构造路径(自动适配 Windows 的 \ 和 Mac/Linux 的 /)
p = Path("data") / "raw" / "news.txt" # 用 / 拼接路径,优雅!
print(p) # data\raw\news.txt (Windows)
# 常用属性/方法
p.name # 'news.txt' 文件名
p.stem # 'news' 不含扩展名
p.suffix # '.txt' 扩展名
p.parent # data\raw 所在目录
p.exists() # 是否存在
p.is_file(); p.is_dir() # 是文件/目录
p.absolute() # 绝对路径
# 读写文本(pathlib 直接封装好了)
Path("hello.txt").write_text("你好", encoding="utf-8")
text = Path("hello.txt").read_text(encoding="utf-8")
# 实用:脚本所在目录(不受运行位置影响)
BASE = Path(__file__).resolve().parent
data_path = BASE / "data" / "news.txt"
| 需求 | 老式 os.path | pathlib |
|---|---|---|
| 拼接 | os.path.join(a,b) | a / b |
| 存在 | os.path.exists(p) | p.exists() |
| 文件名 | os.path.basename(p) | p.name |
| 扩展名 | os.path.splitext(p)[1] | p.suffix |
6目录与文件操作
from pathlib import Path
import shutil, os
Path("output").mkdir(exist_ok=True) # 创建目录(已存在不报错)
Path("a/b/c").mkdir(parents=True, exist_ok=True) # 递归创建多级目录
# 遍历目录下的文件
for f in Path("data").iterdir(): # 直接子项
print(f)
for f in Path("data").glob("*.txt"): # 只匹配 .txt
print(f)
for f in Path("data").rglob("*.csv"): # 递归所有层级找 csv
print(f)
# 文件移动/复制/删除(shutil 是高级文件操作库)
shutil.copy("a.txt", "backup/a.txt") # 复制文件
shutil.copytree("dir1", "dir2") # 复制整个目录
shutil.move("old.txt", "new.txt") # 移动/重命名
os.remove("x.txt") # 删除文件
shutil.rmtree("tmp_dir") # 删除整个非空目录(谨慎!)
7JSON:最通用的数据交换格式
NLP 中标注语料、API 返回、配置文件大量使用 JSON。注意类型映射差异:
| JSON | Python |
|---|---|
object {} | dict |
array [] | list |
| string(仅双引号) | str |
| true / false / null | True / False / None |
| 数字 | int / float |
import json
article = {
"id": 1,
"title": "Python入门",
"tags": ["编程", "教程"],
"published": True,
"views": None
}
# 写:Python 对象 → JSON 字符串/文件
json_str = json.dumps(article, ensure_ascii=False, indent=2)
print(json_str)
# ensure_ascii=False:中文原样输出(默认会转成 \uXXXX)
# indent=2:缩进美化
with open("article.json", "w", encoding="utf-8") as f:
json.dump(article, f, ensure_ascii=False, indent=2) # 注意是 dump 不是 dumps
# 读:JSON 文件 → Python 对象
with open("article.json", encoding="utf-8") as f:
data = json.load(f)
print(data["tags"]) # ['编程', '教程']
data2 = json.loads('{"a": 1, "b": [true, null]}') # loads:字符串 → 对象
print(data2) # {'a': 1, 'b': [True, None]}
JSONL(每行一个独立 JSON 对象)是 NLP 语料常见格式,不能整体 json.load:records = [json.loads(line) for line in open(f, encoding="utf-8") if line.strip()]。
8CSV:表格数据
import csv
# 写入(Excel 打开中文 csv 乱码时,用 encoding="utf-8-sig" 带 BOM)
with open("students.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "成绩"])
writer.writerows([["张三", 90], ["李四", 85]])
# DictWriter:按字典写(列名不易错位,推荐)
with open("students2.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=["name", "score"])
writer.writeheader()
writer.writerow({"name": "王五", "score": 77})
# 读取
with open("students.csv", encoding="utf-8-sig") as f:
reader = csv.reader(f)
for row in reader:
print(row) # ['姓名','成绩'] ['张三','90'](值都是字符串)
with open("students.csv", encoding="utf-8-sig") as f:
for row in csv.DictReader(f): # 每行是字典,用列名访问
print(row["姓名"], row["成绩"])
打开 csv 文件统一加 newline=""(避免 Windows 多空行);复杂表格分析建议直接使用 pandas:pd.read_csv() / df.to_csv()。
9pickle:Python 对象的专属序列化
pickle 能把几乎任意 Python 对象(训练好的模型、复杂字典)序列化为字节,机器学习保存模型常用(joblib 基于它):
import pickle
model_data = {"vocab": {"我": 0, "爱": 1}, "acc": 0.92}
with open("model.pkl", "wb") as f: # 二进制模式 wb
pickle.dump(model_data, f)
with open("model.pkl", "rb") as f:
loaded = pickle.load(f)
print(loaded["acc"]) # 0.92
绝对不要 pickle.load 来源不可信的文件——反序列化时可执行任意代码,是严重安全漏洞。跨语言交换数据用 JSON/Parquet;保存 sklearn 模型可用 joblib,原理与风险相同。
10综合案例:语料文件批处理
from pathlib import Path
import json
def load_corpus(folder):
"""读取文件夹下所有 .txt,返回 [{file, text}, ...]"""
records = []
for path in Path(folder).glob("*.txt"):
text = path.read_text(encoding="utf-8")
records.append({"file": path.name, "text": text.strip()})
return records
def save_jsonl(records, out_path):
"""把语料保存为 JSONL(每行一个 JSON)。"""
with open(out_path, "w", encoding="utf-8") as f:
for r in records:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
def load_jsonl(path):
with open(path, encoding="utf-8") as f:
return [json.loads(line) for line in f if line.strip()]
# corpus = load_corpus("data/raw")
# save_jsonl(corpus, "data/corpus.jsonl")
# for r in load_jsonl("data/corpus.jsonl"):
# print(r["file"], len(r["text"]))
11本章小结与练习
📌 关键结论
- 文本文件一律
with open(..., encoding="utf-8");w覆盖、a追加。 - 大文件逐行
for line in f,不要 read() 全读。 - 路径操作用
pathlib的Path /写法;遍历用 glob/rglob。 - JSON 用
dump/load(文件)、dumps/loads(字符串),中文加ensure_ascii=False。 - pickle 方便但只加载可信文件。