Python 文件操作(十二):文件与目录的读写

Hello,大家好,我是 Ivan。

我们前面写的商品、成绩和用户信息,都是保存在变量里的。程序正常运行的时候没有什么问题,但是程序停止运行,这些数据也就跟着没了。在实际开发过程中,配置、日志、接口返回结果,还有程序生成的文本,不可能每次启动程序以后重新写一遍,所以就需要把它们保存到文件里。

思维导图

那今天我就以数据保存为切入点,带大家讲解 Python 的文件与目录操作,包括文件读取、内容写入、路径处理、目录遍历,以及文件的复制和移动。最后再写一个文本整理脚本,把这些操作放到一起使用。

一、先准备一个文本文件

新建一个项目目录,结构如下:

复制代码
file_demo/
├─ main.py
└─ data/
   └─ notes.txt

notes.txt 中写入几行内容:

复制代码
学习 Python 文件操作
整理项目中的文本文件
把处理结果保存下来

文件已经准备好了,接下来在 main.py 中读取它:

python 复制代码
file = open("data/notes.txt", "r", encoding="utf-8")

content = file.read()
print(content)

file.close()

open() 用来打开文件。第一个参数是文件路径,"r" 表示读取,encoding="utf-8" 指定文本编码。

文件打开以后,read() 会读取其中的内容,并返回一个字符串。操作完成以后还要调用 close() 关闭文件,把占用的系统资源释放掉。

二、使用 with 自动关闭文件

上面的写法可以正常运行,不过只要中间发生异常,最后的 close() 就可能执行不到。平时读写文件,更常见的是使用 with

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    content = file.read()

print(content)

进入 with 代码块时,Python 会打开文件。代码块执行结束后,文件会自动关闭,我们不用再单独写 file.close()

这里的 file 只是一个普通变量名,也可以改成其他名字:

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as notes_file:
    content = notes_file.read()

不过,文件对象离开 with 以后已经关闭,后面不能继续调用它的 read()write()。需要用到的内容,应该在代码块中读取出来,再交给外面的变量。

三、文件内容有几种读取方式

read() 会一次读取整个文件:

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    content = file.read()

print(type(content))
print(content)

如果只想读取一行,可以使用 readline()

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    first_line = file.readline()
    second_line = file.readline()

print(first_line)
print(second_line)

文件对象会记住当前读到了哪里。第一次调用拿到第一行,第二次调用会接着读取下一行。

readlines() 会读取所有行,并返回一个列表:

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    lines = file.readlines()

print(lines)

你可能会看到这样的结果:

python 复制代码
[
    "学习 Python 文件操作\n",
    "整理项目中的文本文件\n",
    "把处理结果保存下来\n",
]

每一行末尾的 \n 是换行符。想去掉首尾空白和换行,可以使用 strip()

python 复制代码
for line in lines:
    print(line.strip())

处理日志或者体积较大的文本时,一般不需要一次把全部内容读进内存,可以直接遍历文件:

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    for line in file:
        print(line.strip())

这种写法每次处理一行,文件很大时也不会一下占用太多内存。

四、为什么要写 encoding

文本保存到磁盘时,实际存储的是字节。程序读取文件,需要按照对应的编码把字节还原成文字。如果保存和读取使用的编码不一致,就可能出现乱码,严重时会直接报错。

python 复制代码
with open("data/notes.txt", "r", encoding="utf-8") as file:
    content = file.read()

自己创建的文本文件,建议统一使用 UTF-8。Windows 上如果省略 encoding,Python 可能跟随系统默认编码,同一份代码放到其他电脑上运行,结果不一定相同。

如果拿到的是旧文件,也有可能使用 GBK:

python 复制代码
with open("data/old_notes.txt", "r", encoding="gbk") as file:
    content = file.read()

这里不能看到乱码就随便换编码。应该先确认文件原本使用的编码,再选择对应参数。

五、向文件中写入内容

把模式改成 "w",就可以写入文件:

python 复制代码
with open("data/result.txt", "w", encoding="utf-8") as file:
    file.write("文件处理完成\n")
    file.write("共处理 3 条记录\n")

如果 result.txt 不存在,Python 会创建它。如果文件已经存在,"w" 会先清空原来的内容,再写入新内容。

write() 不会自动换行:

python 复制代码
with open("data/result.txt", "w", encoding="utf-8") as file:
    file.write("第一行")
    file.write("第二行")

文件中得到的是:

复制代码
第一行第二行

需要换行时,要自己加上 \n。另外,write() 只能写入字符串,数字需要先转换:

python 复制代码
count = 3

with open("data/result.txt", "w", encoding="utf-8") as file:
    file.write(f"共处理 {count} 条记录\n")

一次写入多行可以使用 writelines()

python 复制代码
lines = [
    "第一条记录\n",
    "第二条记录\n",
    "第三条记录\n",
]

with open("data/result.txt", "w", encoding="utf-8") as file:
    file.writelines(lines)

writelines() 同样不会自动添加换行,所以列表中的每个字符串还要保留 \n

六、r、w、a 和 x 有什么区别

打开文件时,第二个参数决定了接下来可以做什么。

模式 作用 文件不存在时 文件已存在时
r 读取文件 报错 从头读取
w 写入文件 创建文件 清空原内容
a 追加内容 创建文件 从末尾继续写
x 创建新文件 创建文件 报错

保存日志时,一般不能覆盖之前的记录,可以使用 "a"

python 复制代码
with open("data/app.log", "a", encoding="utf-8") as file:
    file.write("程序启动\n")

每运行一次,内容都会加到文件末尾。

如果想确保程序不会覆盖已有文件,可以使用 "x"

python 复制代码
with open("data/report.txt", "x", encoding="utf-8") as file:
    file.write("第一次生成报告\n")

report.txt 已经存在时,这段代码会抛出 FileExistsError。这种模式适合生成不能重复覆盖的文件。

七、使用 pathlib 处理路径

前面的代码直接使用了字符串路径:

python 复制代码
"data/notes.txt"

路径简单时这样写没有问题。目录多起来以后,可以使用标准库里的 pathlib

python 复制代码
from pathlib import Path

file_path = Path("data") / "notes.txt"

with file_path.open("r", encoding="utf-8") as file:
    print(file.read())

Path("data") / "notes.txt" 会拼接出完整路径。这里的 /Path 对象提供的路径运算,不是数学除法。

如果代码可能从不同目录启动,最好根据当前脚本的位置定位文件:

python 复制代码
from pathlib import Path

base_dir = Path(__file__).resolve().parent
file_path = base_dir / "data" / "notes.txt"

print(file_path)

__file__ 表示当前 Python 文件,resolve() 得到绝对路径,parent 取它所在的目录。这样在项目外面运行脚本时,也不会因为终端位置变化而找错文件。

八、直接读取和写入文本

Path 还提供了两个快捷方法:

python 复制代码
from pathlib import Path

file_path = Path("data/notes.txt")

content = file_path.read_text(encoding="utf-8")
print(content)

写入文本可以使用 write_text()

python 复制代码
result_path = Path("data/result.txt")

result_path.write_text(
    "文件处理完成\n共处理 3 条记录\n",
    encoding="utf-8",
)

这两个方法内部也会完成文件的打开和关闭,适合一次读完或一次写完的小文件。需要逐行处理、追加内容,或者对打开过程进行更多控制时,还是使用 open() 更方便。

九、创建目录并判断路径

创建目录可以使用 mkdir()

python 复制代码
from pathlib import Path

output_dir = Path("output")
output_dir.mkdir()

目录已经存在时,再运行一次会报错。项目代码里通常会这样写:

python 复制代码
output_dir.mkdir(exist_ok=True)

如果中间还有多层目录,可以加上 parents=True

python 复制代码
report_dir = Path("output") / "2026" / "reports"
report_dir.mkdir(parents=True, exist_ok=True)

判断路径状态时,可以使用下面几个方法:

python 复制代码
path = Path("data/notes.txt")

print(path.exists())
print(path.is_file())
print(path.is_dir())

exists() 判断路径是否存在,is_file() 判断它是不是文件,is_dir() 判断它是不是目录。它们返回的都是布尔值。

十、查看路径中的文件信息

Path 对象可以直接取出文件名、扩展名和上级目录:

python 复制代码
from pathlib import Path

file_path = Path("data/archive/notes.txt")

print(file_path.name)
print(file_path.stem)
print(file_path.suffix)
print(file_path.parent)

输出结果是:

复制代码
notes.txt
notes
.txt
data/archive

修改扩展名可以使用 with_suffix()

python 复制代码
markdown_path = file_path.with_suffix(".md")
print(markdown_path)

得到的路径是 data/archive/notes.md。这个操作只是生成一个新的路径对象,不会直接修改磁盘上的文件。

十一、遍历目录中的文件

假设 data 目录中有下面这些内容:

复制代码
data/
├─ a.txt
├─ b.txt
├─ products.csv
└─ archive/
   └─ old.txt

iterdir() 可以查看当前目录下的直接子项:

python 复制代码
from pathlib import Path

data_dir = Path("data")

for path in data_dir.iterdir():
    print(path)

这里只会看到 a.txtb.txtproducts.csvarchive,不会继续进入 archive

只找当前目录里的 .txt 文件,可以使用 glob()

python 复制代码
for path in data_dir.glob("*.txt"):
    print(path)

连子目录一起查找,可以使用 rglob()

python 复制代码
for path in data_dir.rglob("*.txt"):
    print(path)

这次 archive/old.txt 也会被找到。遍历得到的顺序可能受到操作系统和文件系统影响,需要固定顺序时可以配合 sorted()

python 复制代码
for path in sorted(data_dir.rglob("*.txt")):
    print(path)

十二、重命名和删除文件

rename() 可以修改文件名,也可以把文件移动到同一磁盘中的其他位置:

python 复制代码
from pathlib import Path

old_path = Path("data/notes.txt")
new_path = Path("data/python_notes.txt")

old_path.rename(new_path)

删除文件使用 unlink()

python 复制代码
file_path = Path("data/temp.txt")

if file_path.exists() and file_path.is_file():
    file_path.unlink()

删除空目录可以使用 rmdir()

python 复制代码
empty_dir = Path("data/empty")
empty_dir.rmdir()

目录中还有文件时,rmdir() 会报错。删除操作通常无法像编辑器撤销那样恢复,所以路径来自用户输入或配置文件时,应该先打印并检查,不要直接执行。

十三、复制和移动文件

复制、移动文件时,可以使用 shutil

python 复制代码
from pathlib import Path
import shutil

source = Path("data/notes.txt")
backup_dir = Path("backup")
backup_dir.mkdir(exist_ok=True)

shutil.copy2(source, backup_dir / source.name)

copy2() 会复制文件内容,并尽量保留修改时间等元数据。

移动文件使用 move()

python 复制代码
source = Path("data/result.txt")
target = Path("output/result.txt")

target.parent.mkdir(parents=True, exist_ok=True)
shutil.move(source, target)

复制整个目录可以使用 copytree()

python 复制代码
shutil.copytree("data", "data_backup", dirs_exist_ok=True)

shutil.rmtree() 可以递归删除整个目录,包括里面的所有文件:

python 复制代码
shutil.rmtree("data_backup")

这行代码影响的范围比 unlink() 大很多。实际项目里使用它时,必须确认目标路径,尤其不要把未经检查的字符串直接传进去。

十四、读取二进制文件

文本文件可以指定编码,图片、音频和压缩包保存的是二进制数据,需要使用 "rb""wb"

python 复制代码
with open("data/photo.png", "rb") as source:
    image_data = source.read()

with open("output/photo_copy.png", "wb") as target:
    target.write(image_data)

这段代码会复制图片内容。二进制模式返回的是 bytes,不需要设置 encoding

不过,复制文件时直接使用 shutil.copy2() 更方便。这里写 rbwb,主要是为了看清文本模式与二进制模式的区别。以后处理上传文件、图片内容或网络响应时,还会遇到字节数据。

十五、常见的文件操作错误

1. 相对路径找不到文件

复制代码
FileNotFoundError: [Errno 2] No such file or directory

遇到这个错误,可以先打印当前工作目录:

python 复制代码
from pathlib import Path

print(Path.cwd())

相对路径是从当前工作目录开始计算的,不一定是 Python 文件所在的目录。项目里的固定资源,可以使用 Path(__file__).resolve().parent 定位。

2. 使用 w 覆盖了原文件

"w" 打开文件时会清空已有内容。只是想继续记录日志,应该使用 "a"。不确定文件能不能覆盖时,可以先用 exists() 检查,或者选择 "x"

3. 忘记添加换行符

连续调用 write()writelines() 都不会自动换行。写入多行内容时,要自己在字符串末尾添加 \n

4. 把目录当成文件打开

python 复制代码
path = Path("data")

with path.open("r", encoding="utf-8") as file:
    print(file.read())

data 是目录,这样会出现 IsADirectoryError。路径来自外部时,可以先使用 is_file()is_dir() 判断。

5. 文件正在被其他程序占用

Windows 上删除或移动文件时,如果它正在被编辑器、播放器或者其他程序占用,可能会出现 PermissionError。这时要先关闭占用文件的程序,再重新执行代码。

十六、综合示例:合并目录中的文本

现在做一个简单的文本整理脚本。它会找到 input 目录里的所有 .txt 文件,按照文件名排序,再把内容合并到 output/merged.txt

项目结构如下:

复制代码
file_demo/
├─ merge.py
├─ input/
│  ├─ a.txt
│  ├─ b.txt
│  └─ c.txt
└─ output/

merge.py 中写入:

python 复制代码
from pathlib import Path

base_dir = Path(__file__).resolve().parent
input_dir = base_dir / "input"
output_file = base_dir / "output" / "merged.txt"

output_file.parent.mkdir(parents=True, exist_ok=True)

text_files = sorted(input_dir.glob("*.txt"))

with output_file.open("w", encoding="utf-8") as output:
    for file_path in text_files:
        content = file_path.read_text(encoding="utf-8")

        output.write(f"===== {file_path.name} =====\n")
        output.write(content.rstrip())
        output.write("\n\n")

print(f"合并完成:{output_file}")

这里使用 __file__ 定位项目目录,运行脚本的位置发生变化也不会影响路径。glob("*.txt") 只查找 input 目录中的文本文件,sorted() 让每次合并的顺序保持一致。

写入时先加上原文件名,再写入正文。rstrip() 会去掉文本末尾多余的换行,随后统一补上两个 \n,不同文件之间就会留出一行。

十七、练习

练习一:统计文本行数

读取一个文本文件,统计其中共有多少行。空行也要计算在内,最后打印文件名和行数。

练习二:保存用户输入

让用户连续输入三条待办事项,把它们写入 todo.txt,每条内容单独占一行。再次运行程序时,原来的内容不能被覆盖。

练习三:整理指定类型的文件

遍历一个目录,把所有 .txt 文件移动到 texts 子目录中。移动前要创建目录,并检查当前路径确实是文件。

练习四:批量修改扩展名

找到目录中的所有 .log 文件,把扩展名改成 .txt。可以使用 glob()with_suffix()rename()

练习五:备份目录

编写一个脚本,把 data 目录复制成 data_backup。目标目录已经存在时,仍然可以继续复制。

练习六:合并子目录中的文本

修改综合示例,让程序连 input 的子目录一起查找 .txt 文件。合并结果中除了文件名,还要写出文件相对于 input 的路径。

文件读写的代码本身并不复杂,容易出问题的地方通常是路径、打开模式和编码。项目里需要操作文件时,先把目标路径打印出来,再确认使用的是读取、覆盖还是追加模式,很多错误都能提前发现。

相关推荐
神仙别闹9 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
Y幽谷客9 小时前
Python加载本地大模型(Qwen3.5 8B)
python·大模型
伞伞悦读10 小时前
【第36期】Python 目录与路径详解:pathlib、文件遍历、创建、复制、移动和删除风险
开发语言·python
线上放牧人10 小时前
Windows删除图标缓存
windows·python·pyqt
qq_54702617911 小时前
Python 变量和简单数据类型
python
智搜广告12 小时前
智搜广告:科技行业AI回答优化公司如何破局
大数据·python·elasticsearch·geo
IpdataCloud12 小时前
AI智能体调用工具怎么核验来源IP?归属地、网络类型与代理风险识别(含Python代码)
数据库·python·tcp/ip
2601_9669496513 小时前
只拿到股票代码还不够:量化程序到底还需要哪些标的信息?——从数据建模开始理解股票元数据
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
蒸鱼Yuzheng13 小时前
Python 游戏测试开发怎么准备:日志解析、接口校验、并发与可维护性
自动化测试·python·测试开发·面试题·游戏测试