Python 基础篇(七):第七章: 文件操作


第七章. 文件操作:读写数据

在前面的章节中,我们学习了变量、数据容器、条件判断、循环。但到目前为止,我们的程序一旦关闭,所有数据都会丢失。这一章,我们将学习文件操作,让程序能够保存和读取数据。


7.1. 为什么需要文件操作?

7.1.1. 数据持久化的需求

假设你写了一个学生成绩管理程序,用列表存储所有学生的成绩:

1
2
3
4
5
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78}
]

但这样有个问题:程序关闭后,所有数据都会丢失。下次运行程序时,又要重新输入所有数据。

文件操作可以解决这个问题:把数据保存到文件中,下次运行程序时再从文件中读取。


7.1.2. 文件操作的应用场景

场景 1:配置文件

程序的配置信息(如数据库连接、API 密钥)通常保存在配置文件中。

场景 2:日志记录

程序运行时的重要信息(如错误、警告)需要记录到日志文件中,方便后续排查问题。

场景 3:数据导入导出

从 Excel、CSV 文件中读取数据,或者将处理结果导出到文件中。

场景 4:数据备份

定期将重要数据保存到文件中,防止数据丢失。


7.2. 文件的打开与关闭

7.2.1. open() 函数的基本用法

open() 函数用于打开文件,返回一个文件对象:

1
2
3
4
5
6
7
8
9
# 打开文件
file = open("data.txt", "r", encoding="utf-8")

# 读取内容
content = file.read()
print(content)

# 关闭文件
file.close()

关键点

  • 第一个参数:文件路径
  • 第二个参数:打开模式(r 表示只读)
  • encoding 参数:指定文件编码(推荐使用 utf-8
  • 使用完文件后,必须调用 close() 关闭文件

7.2.2. 文件打开模式详解

模式说明文件不存在文件存在常见用途
r只读模式报错从头读取读取配置文件
w只写模式创建新文件清空内容生成报告
a追加模式创建新文件在末尾追加日志记录
r+读写模式报错可读可写需要同时读写
w+读写模式创建新文件清空内容先写后读
a+追加读写创建新文件追加且可读日志分析

常用模式

  • r:读取文件(最常用)
  • w:写入文件(会清空原内容,小心使用)
  • a:追加内容(不会清空原内容,更安全)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 只读模式
file = open("data.txt", "r", encoding="utf-8")
content = file.read()
file.close()

# 只写模式(会清空原内容)
file = open("output.txt", "w", encoding="utf-8")
file.write("新内容")
file.close()

# 追加模式(不会清空原内容)
file = open("log.txt", "a", encoding="utf-8")
file.write("新日志\n")
file.close()

7.2.3. with 语句:自动关闭文件

手动调用 close() 容易忘记,而且如果程序出错,close() 可能不会执行。推荐使用 with 语句,它会自动关闭文件:

1
2
3
4
5
# 使用 with 语句(推荐)
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
print(content)
# 退出 with 块后,文件自动关闭

with 语句的优势

  1. 自动关闭文件,不需要手动调用 close()
  2. 即使程序出错,文件也会被正确关闭
  3. 代码更简洁

7.2.4. 常见错误与避坑指南

错误 1:文件不存在

1
2
# ❌ 错误:文件不存在
file = open("not_exist.txt", "r") # FileNotFoundError

如何避免? 使用 wa 模式会自动创建文件。


错误 2:忘记关闭文件

1
2
3
4
# ❌ 错误:忘记关闭文件
file = open("data.txt", "r")
content = file.read()
# 忘记 file.close()

如何避免? 使用 with 语句。


错误 3:编码问题

1
2
# ❌ 错误:没有指定编码
file = open("data.txt", "r") # 可能出现乱码

如何避免? 始终指定 encoding="utf-8"


7.3. 文件的读取

7.3.1. read():一次性读取全部内容

read() 方法会一次性读取文件的全部内容,返回一个字符串:

1
2
3
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
print(content)

适用场景:文件较小(几 MB 以内)。

注意:如果文件很大(如几百 MB),一次性读取会占用大量内存。


7.3.2. readline():逐行读取

readline() 方法每次读取一行,返回一个字符串(包含换行符 \n):

1
2
3
4
5
with open("data.txt", "r", encoding="utf-8") as file:
line1 = file.readline()
line2 = file.readline()
print(line1) # 第一行(包含 \n)
print(line2) # 第二行(包含 \n)

适用场景:需要逐行处理文件内容。


7.3.3. readlines():读取所有行到列表

readlines() 方法读取所有行,返回一个列表,每个元素是一行(包含换行符 \n):

1
2
3
4
5
6
7
with open("data.txt", "r", encoding="utf-8") as file:
lines = file.readlines()
print(lines) # ['第一行\n', '第二行\n', '第三行\n']

# 去除换行符
lines = [line.strip() for line in lines]
print(lines) # ['第一行', '第二行', '第三行']

适用场景:需要对所有行进行处理。


7.3.4. 遍历文件对象:最佳实践

推荐方式:直接遍历文件对象,这是最节省内存的方式:

1
2
3
with open("data.txt", "r", encoding="utf-8") as file:
for line in file:
print(line.strip()) # 去除换行符

为什么推荐?

  • 不会一次性加载整个文件到内存
  • 适用于任意大小的文件
  • 代码简洁

7.3.5. 常见错误与避坑指南

错误 1:重复读取

1
2
3
4
with open("data.txt", "r", encoding="utf-8") as file:
content1 = file.read()
content2 = file.read() # 空字符串
print(content2) # 输出:(空)

为什么? 第一次 read() 后,文件指针已经到达文件末尾。

如何避免? 使用 seek(0) 重置文件指针:

1
2
3
4
with open("data.txt", "r", encoding="utf-8") as file:
content1 = file.read()
file.seek(0) # 重置文件指针
content2 = file.read()

错误 2:忘记去除换行符

1
2
3
with open("data.txt", "r", encoding="utf-8") as file:
for line in file:
print(line) # 每行后面有两个换行符

如何避免? 使用 strip() 去除换行符:

1
2
3
with open("data.txt", "r", encoding="utf-8") as file:
for line in file:
print(line.strip())

7.4. 文件的写入

7.4.1. write():写入字符串

write() 方法写入一个字符串到文件:

1
2
3
with open("output.txt", "w", encoding="utf-8") as file:
file.write("第一行\n")
file.write("第二行\n")

注意write() 不会自动添加换行符,需要手动添加 \n


7.4.2. writelines():写入多行

writelines() 方法写入一个字符串列表到文件:

1
2
3
4
lines = ["第一行\n", "第二行\n", "第三行\n"]

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

注意writelines() 也不会自动添加换行符。


7.4.3. 追加模式 vs 覆盖模式

覆盖模式(w:会清空文件原有内容

1
2
3
4
5
6
7
8
9
# 第一次写入
with open("data.txt", "w", encoding="utf-8") as file:
file.write("第一行\n")

# 第二次写入(会清空原内容)
with open("data.txt", "w", encoding="utf-8") as file:
file.write("第二行\n")

# 文件内容:只有"第二行"

追加模式(a:不会清空文件原有内容

1
2
3
4
5
6
7
8
9
# 第一次写入
with open("data.txt", "w", encoding="utf-8") as file:
file.write("第一行\n")

# 第二次写入(追加)
with open("data.txt", "a", encoding="utf-8") as file:
file.write("第二行\n")

# 文件内容:第一行\n第二行\n

7.4.4. 常见错误与避坑指南

错误 1:使用 w 模式误删数据

1
2
3
4
# ❌ 错误:使用 w 模式会清空原内容
with open("important.txt", "w", encoding="utf-8") as file:
file.write("新内容")
# 原内容被清空了!

如何避免? 如果不确定,使用 a 模式更安全。


错误 2:忘记添加换行符

1
2
3
4
with open("output.txt", "w", encoding="utf-8") as file:
file.write("第一行")
file.write("第二行")
# 文件内容:第一行第二行(没有换行)

如何避免? 手动添加 \n


7.5. 文件指针与定位

7.5.1. tell():获取当前位置

tell() 方法返回文件指针的当前位置(字节数):

1
2
3
4
with open("data.txt", "r", encoding="utf-8") as file:
print(file.tell()) # 0(文件开头)
file.read(5)
print(file.tell()) # 5(读取了 5 个字符)

7.5.2. seek():移动文件指针

seek() 方法移动文件指针到指定位置:

1
2
3
with open("data.txt", "r", encoding="utf-8") as file:
file.seek(0) # 移动到文件开头
content = file.read()

语法file.seek(offset, whence)

  • offset:偏移量(字节数)
  • whence:参考位置
    • 0:文件开头(默认)
    • 1:当前位置
    • 2:文件末尾

7.5.3. 实战:读取文件的最后几行

1
2
3
4
5
6
7
8
9
10
def read_last_lines(filename, n=10):
"""读取文件的最后 n 行"""
with open(filename, "r", encoding="utf-8") as file:
lines = file.readlines()
return lines[-n:]

# 使用
last_lines = read_last_lines("log.txt", 5)
for line in last_lines:
print(line.strip())

7.6. 文件路径操作

7.6.1. os.path 模块的基本用法

os.path 模块提供了文件路径操作的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os

# 获取当前工作目录
current_dir = os.getcwd()
print(current_dir)

# 获取文件的绝对路径
abs_path = os.path.abspath("data.txt")
print(abs_path)

# 获取文件所在目录
dir_path = os.path.dirname(abs_path)
print(dir_path)

# 获取文件名
filename = os.path.basename(abs_path)
print(filename)

7.6.2. 路径拼接与分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os

# 路径拼接
path = os.path.join("folder", "subfolder", "file.txt")
print(path) # folder/subfolder/file.txt(Windows 上是 \)

# 路径分割
dir_path, filename = os.path.split(path)
print(dir_path) # folder/subfolder
print(filename) # file.txt

# 分离文件名和扩展名
name, ext = os.path.splitext(filename)
print(name) # file
print(ext) # .txt

7.6.3. 路径检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os

# 检查路径是否存在
exists = os.path.exists("data.txt")
print(exists)

# 检查是否是文件
is_file = os.path.isfile("data.txt")
print(is_file)

# 检查是否是目录
is_dir = os.path.isdir("folder")
print(is_dir)

# 获取文件大小(字节)
size = os.path.getsize("data.txt")
print(size)

7.6.4. pathlib:现代化的路径操作

pathlib 是 Python 3.4+ 引入的现代化路径操作库,使用面向对象的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pathlib import Path

# 创建路径对象
path = Path("data.txt")

# 检查路径是否存在
print(path.exists())

# 检查是否是文件
print(path.is_file())

# 读取文件内容
content = path.read_text(encoding="utf-8")
print(content)

# 写入文件内容
path.write_text("新内容", encoding="utf-8")

# 路径拼接(使用 / 运算符)
new_path = Path("folder") / "subfolder" / "file.txt"
print(new_path)

# 获取文件名
print(path.name) # data.txt

# 获取文件名(不含扩展名)
print(path.stem) # data

# 获取扩展名
print(path.suffix) # .txt

# 获取父目录
print(path.parent)

pathlib vs os.path

功能os.pathpathlib
路径拼接os.path.join(a, b)Path(a) / b
获取文件名os.path.basename(path)path.name
检查存在os.path.exists(path)path.exists()
读取文件open(path).read()path.read_text()

推荐:新项目使用 pathlib,它更简洁、更直观。


7.7. 目录操作

7.7.1. 创建目录

1
2
3
4
5
6
7
8
9
10
11
12
import os

# 创建单个目录
os.mkdir("new_folder")

# 创建多级目录
os.makedirs("parent/child/grandchild")

# 使用 pathlib
from pathlib import Path
Path("new_folder").mkdir(exist_ok=True)
Path("parent/child/grandchild").mkdir(parents=True, exist_ok=True)

参数说明

  • exist_ok=True:如果目录已存在,不报错
  • parents=True:自动创建父目录

7.7.2. 删除目录

1
2
3
4
5
6
7
8
9
10
11
12
import os
import shutil

# 删除空目录
os.rmdir("empty_folder")

# 删除目录及其所有内容(危险操作!)
shutil.rmtree("folder")

# 使用 pathlib
from pathlib import Path
Path("empty_folder").rmdir()

7.7.3. 列出目录内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os

# 列出目录中的所有文件和文件夹
entries = os.listdir(".")
print(entries)

# 过滤文件
files = [f for f in entries if os.path.isfile(f)]
print(files)

# 使用 pathlib
from pathlib import Path
path = Path(".")
for item in path.iterdir():
if item.is_file():
print(f"文件: {item.name}")
elif item.is_dir():
print(f"目录: {item.name}")

7.7.4. 遍历目录树

os.walk() 可以递归遍历目录树:

1
2
3
4
5
6
7
import os

for root, dirs, files in os.walk("."):
print(f"当前目录: {root}")
print(f"子目录: {dirs}")
print(f"文件: {files}")
print("-" * 40)

返回值

  • root:当前目录路径
  • dirs:当前目录下的子目录列表
  • files:当前目录下的文件列表

7.8. 文件的复制、移动和删除

7.8.1. shutil 模块简介

shutil 模块提供了高级的文件操作函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import shutil

# 复制文件
shutil.copy("source.txt", "destination.txt")

# 复制文件(保留元数据)
shutil.copy2("source.txt", "destination.txt")

# 复制目录
shutil.copytree("source_folder", "destination_folder")

# 移动文件或目录
shutil.move("old_path", "new_path")

# 删除目录及其所有内容
shutil.rmtree("folder")

7.8.2. 复制文件

1
2
3
4
5
6
7
import shutil

# copy():复制文件,不保留元数据
shutil.copy("source.txt", "destination.txt")

# copy2():复制文件,保留元数据(修改时间等)
shutil.copy2("source.txt", "destination.txt")

7.8.3. 移动文件

1
2
3
4
5
6
7
import shutil

# 移动文件
shutil.move("file.txt", "folder/file.txt")

# 重命名文件
shutil.move("old_name.txt", "new_name.txt")

7.8.4. 删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os

# 删除文件
os.remove("file.txt")

# 删除文件(如果存在)
if os.path.exists("file.txt"):
os.remove("file.txt")

# 使用 pathlib
from pathlib import Path
path = Path("file.txt")
if path.exists():
path.unlink()

7.9. 本章小结

本章学习了文件操作的核心知识,从文件的打开、读取、写入,到文件路径操作、目录操作和文件管理。

核心要点

知识点使用场景关键语法
文件打开读写文件open(file, mode, encoding)
with 语句自动关闭文件with open(...) as file:
文件读取读取文件内容read(), readline(), readlines()
文件写入写入文件内容write(), writelines()
文件指针定位文件位置tell(), seek()
路径操作处理文件路径os.path, pathlib
目录操作管理目录mkdir(), listdir(), os.walk()
文件管理复制移动删除shutil.copy(), shutil.move(), os.remove()

最佳实践

  • 始终使用 with 语句打开文件
  • 始终指定 encoding="utf-8"
  • 使用 a 模式比 w 模式更安全
  • 新项目推荐使用 pathlib