CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Python 语言基础/课时/文件读写
RawBlame
课时目录12 节

Python 语言基础

  1. 01Python 是什么
  2. 02语法与缩进
  3. 03类型与变量
  4. 04条件与循环
  5. 05函数
  6. 06列表字典与集合
  7. 07字符串处理
  8. 08文件读写
  9. 09模块与 pip
  10. 10类与对象入门
  11. 11异常处理
  12. 12综合练习与下一步

文件读写

open、with、路径 pathlib 入门。

30 分钟

文件读写

这一课你将能做到

  1. 用 with open(...) 正确读写文本文件,说清为什么 with 能自动关闭文件
  2. 用 pathlib 处理路径:拼接、判断存在、创建目录、批量列举文件
  3. 读懂并写出「逐行处理大文件」和「读写 JSON」这两个高频场景的代码

为什么要学

前面几课的数据都活在内存里,程序一关全没了。文件读写是脚本「记住东西」的第一种方式:批量处理日志、导出报表、保存配置、持久化爬虫结果,全靠它。而且「文件 + 循环 + 字符串」三件套正是 Python 脚本最经典的形态——第 12 课的综合练习直接用这一课的产出。

人话:open 就是找图书管理员借书(打开文件),read 是翻阅,write 是往里写,close 是还书。with 语句就是「自动还书提醒」——不管中途发生什么,出了作用域管理员一定把书收回去。

先建立图像

open() 的三种基本模式:

r  →  read    读(文件必须存在,光标在开头)
w  →  write   写(清空原内容!不存在则新建)
a  →  append  追加(保留原内容,写在末尾)

加 b 是二进制模式:rb / wb(图片、视频用)
文本模式强烈建议显式指定 encoding="utf-8"

with 语句结构:

with open("demo.txt", "w", encoding="utf-8") as f:
    f.write("内容")        # with 块内:文件处于打开状态
# with 块外:文件必定已自动关闭

跟着做

步骤 1:写入第一个文件

新建 files_demo.py,在脚本所在目录运行:

with open("demo.txt", "w", encoding="utf-8") as f:
    f.write("第一行\n")
    f.write("第二行\n")
    f.writelines(["第三行\n", "第四行\n"])   # 接收字符串列表

print("写入完成")

运行后打开脚本目录里的 demo.txt,应有 4 行文本。注意 w 模式每次运行都会清空重写,想保留旧内容追加用 a 模式:

with open("demo.txt", "a", encoding="utf-8") as f:
    f.write("追加的第五行\n")

输出(终端):

写入完成

步骤 2:读文件的三种方式

# 方式一:一次性读全部(小文件)
with open("demo.txt", "r", encoding="utf-8") as f:
    content = f.read()
print(repr(content[:12]))      # '第一行\n第二行\n第三' —— \n 也在里面

# 方式二:按行读成列表
with open("demo.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()      # 每行末尾带着 \n
print(len(lines))              # 5

# 方式三:逐行遍历(推荐,内存友好,文件多大都不怕)
with open("demo.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())    # strip 去掉行尾换行符

输出:

'第一行\n第二行\n第三'
5
第一行
第二行
第三行
第四行
追加的第五行

步骤 3:with 的意义——亲眼验证自动关闭

f = open("demo.txt", "r", encoding="utf-8")
print(f.closed)        # False:文件开着
f.close()
print(f.closed)        # True

# 手动 open 的问题:中间一旦抛异常,close 就执行不到了
# with 则保证「无论是否异常」都关闭:

with open("demo.txt", "r", encoding="utf-8") as f:
    print(f.closed)    # False
print(f.closed)        # True —— 出了 with 块自动关了

输出:

False
True
False
True

规则:文件操作永远用 with,不裸写 open/close。

步骤 4:pathlib 现代路径操作

from pathlib import Path

p = Path("data")                       # 当前目录下的 data
print(p / "raw" / "2026.csv")          # 用 / 拼路径,跨平台自动处理分隔符
# data/raw/2026.csv (Windows 下显示 data\raw\2026.csv)

print(Path.cwd())                      # 当前工作目录
print(Path("demo.txt").exists())       # True(上一节刚建的)
print(Path("demo.txt").is_file())      # True
print(Path("demo.txt").suffix)         # .txt 后缀

# 递归创建目录( parents=True 连父目录一起建)
Path("data/raw/logs").mkdir(parents=True, exist_ok=True)  # 已存在不报错

# 列出目录内容
for f in Path(".").glob("*.py"):       # 当前目录所有 .py
    print(f.name)

# pathlib 也能直接读写(内部仍是 open,小文件场景简洁)
p2 = Path("quick.txt")
p2.write_text("pathlib 快捷写入", encoding="utf-8")
print(p2.read_text(encoding="utf-8"))

输出:

data/raw/2026.csv   (Windows: data\raw\2026.csv)
<当前工作目录绝对路径>
True
True
.txt
files_demo.py
pathlib 快捷写入

pathlib 是 3.4+ 的官方推荐,替代老的 os.path 拼字符串写法——p / "sub" / "file.txt" 一行顶三行。

步骤 5:实战——逐行处理 + 统计

把第 7 课的字符串能力串起来:统计一个文本文件的行数和最长单词行。

from pathlib import Path

# 先准备一份样例文件
Path("notes.txt").write_text(
    "python is powerful\n"
    "files make scripts useful\n"
    "practice every day\n",
    encoding="utf-8",
)

with open("notes.txt", "r", encoding="utf-8") as f:
    total_lines = 0
    longest = ""
    for line in f:
        total_lines += 1
        line = line.strip()
        if len(line) > len(longest):
            longest = line

print(f"共 {total_lines} 行")
print(f"最长的一行:{longest}")

输出:

共 3 行
最长的一行:files make scripts useful

步骤 6:JSON 读写——结构化数据的默认格式

import json
from pathlib import Path

users = [
    {"name": "小明", "age": 18, "tags": ["python", "java"]},
    {"name": "小红", "age": 20, "tags": ["go"]},
]

# 写:dump 到文件,ensure_ascii=False 让中文正常显示
with open("users.json", "w", encoding="utf-8") as f:
    json.dump(users, f, ensure_ascii=False, indent=2)

# 读:从文件 load 回来,类型自动还原(列表/字典/数字)
with open("users.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(type(loaded))              # <class 'list'>
print(loaded[0]["tags"][0])      # python
print(loaded[1]["name"])         # 小红

# 字符串 ↔ JSON 的转换(API 场景常用)
s = json.dumps(users[0], ensure_ascii=False)
print(s)                         # {"name": "小明", "age": 18, ...}
back = json.loads(s)
print(back["age"])               # 18

输出:

<class 'list'>
python
小红
{"name": "小明", "age": 18, "tags": ["python", "java"]}
18

json 是标准库,不用装。json.load 读回来就是 Python 的 dict/list,和第 6 课练的嵌套结构完全对上。

讲清楚要点

  1. 永远 with open:with 依赖文件对象的上下文管理器协议,异常时也会关闭文件。裸 open 一旦异常就泄漏文件句柄,Windows 上还会导致文件被占用删不掉。
  2. encoding="utf-8" 必须显式写:不写时 Python 用系统默认编码——Windows 上常是 GBK,跨机器运行就 UnicodeDecodeError。UTF-8 是事实标准,永远显式。
  3. w 会清空文件:w 模式打开的瞬间原内容就没了,不是等 write 才清。只想加内容用 a;读文件写 r 可以省略(默认)。
  4. 大文件用逐行遍历:for line in f 是惰性的,内存里永远只有一行;read() 和 readlines() 会把全部内容塞进内存,处理几百 MB 日志时会卡爆。
  5. write 不自动换行:和 print 不同,f.write("abc") 不会补 \n,多行内容要自己写换行符。
  6. pathlib 的 / 是路径拼接运算符:Path("a") / "b" / "c.txt",自动处理 / 和 \ 的平台差异。判断存在用 exists(),建目录用 mkdir(parents=True, exist_ok=True)。
  7. JSON 只支持基础类型:dict/list/str/int/float/bool/None 能转,tuple 会变 list,datetime 等对象要先转字符串(isoformat())再存。

你可能会懵的地方

  • 为什么我运行脚本时找不到刚才写的文件? 相对路径基于「运行命令时所在的目录」,不是脚本文件所在目录。在 python-lab 目录外执行 python lab/files_demo.py,文件就写到了 python-lab 外面。解决:cd 到脚本目录再跑,或用 Path(__file__).parent 定位脚本目录。

  • read() 和 readlines() 选哪个? 小文件(几百 KB 内)随手用都行;日志、CSV 这类可能很大的文件一律逐行遍历 for line in f。原则:能惰性就不贪多。

  • 文件不存在时报 FileNotFoundError 怎么优雅处理? 读之前 Path(p).exists() 判断;或用 try/except 捕获 FileNotFoundError(第 11 课)。写模式下 w/a 会自动创建文件,不存在不用慌。

  • 二进制文件(图片、PDF)怎么处理? 模式加 b:with open("img.png", "rb") as f: data = f.read(),读到的是 bytes 不是 str。处理图片本身一般交给 Pillow 等库,脚本里常只做「复制、移动、上传」这类搬运。

  • 追加写入后文件里全挤在一行? a 模式从文件末尾续写,如果原文件最后一行没换行符,新内容会接在同一行。追加前先确认结尾:内容前手动补 \n 或写入时每条都带。

错法对照

错法问题正解
f = open(...) 手动 close异常时 close 不会执行with open(...) as f:
open 不写 encodingWindows 默认 GBK,跨机乱码显式 encoding="utf-8"
用 w 模式「接着写」每次打开清空原内容追加用 a 模式
大文件用 read() 一次读完内存爆炸for line in f 逐行处理
f.write("abc") 后想换行write 不自动补 \n写 "abc\n"
字符串 + 拼 Windows 路径分隔符平台差异、易错Path(a) / b / "c.txt"
json.dump 忘了 ensure_ascii=FalseJSON 文件里中文变 \uXXXX加 ensure_ascii=False

卡点急救

  1. UnicodeDecodeError:文件实际编码和指定编码不符。先确认来源编码(Excel 导出常是 GBK),读取时对应传 encoding="gbk";自己的文件统一存 UTF-8。
  2. PermissionError:文件被别的程序占用(Excel 开着 CSV)、或目标目录只读。关掉占用程序;确认写入路径有权限。
  3. 找不到写入的文件:相对路径相对「运行目录」而非「脚本目录」。用绝对路径或 Path(__file__).parent / "demo.txt"。
  4. 读出来每行末尾多了 \n:正常,文件本身含换行符。line.strip() 或 line.rstrip("\n") 处理。
  5. JSONDecodeError: Expecting value:JSON 文件格式坏(手工编辑弄坏的、或读的是空文件)。先 print(Path(p).read_text(encoding="utf-8")) 目检内容。
  6. 写入后读不到最新内容:写文件的对象还没关闭(缓冲未刷盘)。确认 with 块已退出,或读之前 f.flush()。

面试常问

  • with 语句的原理是什么? 上下文管理器协议:对象实现 __enter__(进入时调用,返回值绑定给 as 变量)与 __exit__(离开时调用,无论是否异常都会执行)。文件对象在 __exit__ 里关闭自己。等价于 try/finally,但语义清晰。自定义类也可实现这两个方法支持 with。

  • 如何读取一个 10GB 的日志文件? 不能 read() 全量载入。逐行遍历(for line in f,惰性加载);或 readline() 控制;或按块读二进制(f.read(chunk_size))配合迭代。关键是内存中只保留一小部分。

  • 文本模式和二进制模式的区别? 文本模式做两件额外的事:解码(bytes→str,按 encoding)与换行符转换(\r\n ↔ \n)。二进制模式原样读写字节,不解码不转换。图片/网络数据用二进制,人类可读文本用文本模式并显式 encoding。

  • r、w、a、x 模式的区别? r 只读(不存在报错);w 只写(清空或新建);a 追加(不存在新建);x 新建写(已存在报 FileExistsError,防覆盖)。加 + 可读写(如 r+)。另有 b 后缀进二进制。

  • pathlib 相比 os.path 的优势? 面向对象:路径是对象而非字符串,/ 运算符拼接跨平台正确;方法内聚(exists、glob、mkdir、read_text 直接在对象上);与 f-string 配合表达清晰。新代码官方推荐 pathlib。

自测三问

  1. w 和 a 模式打开同一个已存在文件,行为差在哪?
  2. 为什么处理大文件必须用 for line in f 而不是 f.read()?写出逐行统计行数的代码。
  3. Path("a") / "b" / "c.txt" 与 os.path.join 相比好在哪?mkdir 防报错的两个参数是什么?

小结一句话

文件操作三板斧:永远 with open、永远 encoding="utf-8"、大文件逐行读;再配上 pathlib 的 / 拼路径和 json 标准库,脚本就能真正把数据留在磁盘上了。

延伸

  • 官方教程 — 读写文件
  • pathlib 官方文档
  • 下一课预告:模块与 pip——import 的机制、__name__ == "__main__" 的真相、虚拟环境与依赖管理。
加载本节练习…
← 上一课字符串处理下一课 →模块与 pip