Python中的文件操作
本文介绍Python中的文件操作。
本指南以“安全”、“高效”和“可读性”为出发点,从基础到实用技巧,逐步讲解相关概念。
YouTube Video
Python中的文件操作
文件操作是从小型脚本到大型应用的基本必备技能。
打开和关闭文件
首先,让我们看看读取和写入文本文件的示例。使用 with 语句(上下文管理器)可以确保文件被正确关闭。
以下代码打开一个文本文件,读取内容,并逐行处理。
1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3 # Read all lines lazily (as an iterator) and process each line.
4 for line in f:
5 print(line.rstrip())- 显式指定
encoding="utf-8"有助于减少平台依赖性问题。 - 即使是大文件,使用
for line in f也能有效节省内存。
写入(覆盖和追加)
写入文件时请注意模式的选择。w 表示覆盖写入,a 表示追加写入。写入时也请使用 with 语句。
以下代码展示了覆盖写入和追加写入的基本示例。
1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3 f.write("First line\n")
4 f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8 f.write("Appended line\n")- 该代码先向
output.txt写入文本,然后向同一文件追加更多内容。"w"模式会覆盖文件,而"a"模式会在现有内容结尾追加新行。 - 如果需要刷新内容,请调用
flush(),但通常在上下文结束时内容会自动刷新。 - 如果有多个进程或线程可能同时写入,则需要考虑独占控制,例如文件锁。
二进制数据的读写
图片和压缩文件应以二进制模式(rb 或 wb)处理。与文本模式不同,二进制模式会忽略 encoding。
以下代码读取一个二进制文件,并以二进制模式复制到另一个文件。
1# Read and write binary files.
2with open("image.png", "rb") as src:
3 data = src.read()
4
5with open("copy.png", "wb") as dst:
6 dst.write(data)- 处理大型二进制文件时,避免使用
read()一次性全部读取;分块读写更加节省内存。
分块处理大文件的示例
对于无法一次放入内存的巨大文件,请使用固定大小的块进行读写。由于这是 I/O 密集型操作,调整缓冲区大小很有帮助。
以下代码以 64KB 为单位分块复制文件。这样可以保持较低内存消耗的同时快速操作。
1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024 # 64 KB
3
4with open("large_source.bin", "rb") as src, open("large_dest.bin", "wb") as dst:
5 while True:
6 chunk = src.read(CHUNK_SIZE)
7 if not chunk:
8 break
9 dst.write(chunk)- 你可以根据网络或磁盘的特性调整块的大小。在固态硬盘上,稍大一些的块大小往往效果更好。
使用 buffering 参数的示例
通过在 open() 函数中指定 buffering 参数,您可以控制 Python 内部使用的缓冲区大小。这样可以进一步优化输入/输出效率。
1# Explicitly set the internal buffer size to 128 KB for faster I/O.
2CHUNK_SIZE = 64 * 1024
3BUFFER_SIZE = 128 * 1024 # 128 KB internal buffer
4
5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src, open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
6 while True:
7 chunk = src.read(CHUNK_SIZE)
8 if not chunk:
9 break
10 dst.write(chunk)- 如果将
buffering的值设置为0,I/O 操作将不会使用缓冲区;1 启用行缓冲,而2及以上的值则使用指定字节大小的缓冲区。 - 一般来说,默认值已经足够,因为操作系统可以高效地处理缓存,但对于非常大的文件或特殊设备,调整此参数可能会更有效。
使用 Pathlib 进行现代文件操作
标准库中的 pathlib 模块使路径处理更加直观。与字符串路径相比,它提高了可读性和安全性。
1from pathlib import Path
2
3path = Path("data") / "notes.txt"
4
5# Ensure parent directory exists.
6path.parent.mkdir(parents=True, exist_ok=True)
7
8# Write and read using pathlib.
9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)- 此代码演示了如何使用
pathlib创建目录、写入文本文件并读取内容。有了Path对象,你可以更加直观和安全地操作路径。 Path提供了如iterdir()和glob()等方便的 API。使用它无需担心不同操作系统下的路径分隔符问题。
临时文件和目录(tempfile)
可以使用 tempfile 安全地创建临时文件。这样可以避免安全竞争条件和命名冲突。
以下代码演示了如何通过临时文件创建临时数据。
1import tempfile
2
3# Create a temporary file that is deleted on close.
4with tempfile.NamedTemporaryFile(
5 mode="w+",
6 encoding="utf-8",
7 delete=True
8) as tmp:
9 tmp.write("temporary data\n")
10 tmp.seek(0)
11 print(tmp.read())
12
13# tmp is deleted here- 该代码创建一个临时文件,写入并读取数据,在
with代码块结束时自动删除。使用tempfile.NamedTemporaryFile,你可以安全且无冲突地处理临时文件。由于指定了delete=True,文件会自动删除。 - 在 Windows 上,可能无法立即用其他句柄打开该文件,因此可以设置
delete=False并自行管理删除。
shutil:用于复制、移动和删除的高级操作
使用 shutil 可以轻松递归地复制、移动和删除文件及目录。
1import shutil
2
3# Copy a file preserving metadata.
4shutil.copy2("source.txt", "dest.txt")
5
6# Move a file or directory.
7shutil.move("old_location", "new_location")
8
9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")shutil.copy2还会复制元数据(如修改时间)。move即使不能重命名,也会退回为文件移动。rmtree是一个危险操作,删除前请务必确认并备份。
文件元数据(os.stat)与权限管理
可以用 os 和 stat 读写文件大小、修改时间和权限等信息。
以下代码使用 os.stat 获取基本文件信息,并用 os.chmod 修改权限。
1import os
2import stat
3from datetime import datetime
4
5st = os.stat("example.txt")
6print("size:", st.st_size)
7print("modified:", datetime.fromtimestamp(st.st_mtime))
8
9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)- 权限在 POSIX 和 Windows 系统上的表现方式不同。如果需要跨平台兼容性,请使用高级 API 或额外编写条件处理逻辑。
文件锁定(独占控制)——Unix 与 Windows 的差异
当多个进程并行访问同一个文件时,需要独占控制。UNIX 使用 fcntl,Windows 使用 msvcrt。
下列代码在 UNIX 系统上用 fcntl.flock 实现写入时的独占锁定。
1# Unix-style file locking example
2import fcntl
3
4with open("output.txt", "a+", encoding="utf-8") as f:
5 fcntl.flock(f, fcntl.LOCK_EX)
6 try:
7 f.write("Safe write\n")
8 f.flush()
9 finally:
10 fcntl.flock(f, fcntl.LOCK_UN)- 该代码在类 UNIX 系统上通过
fcntl.flock获取独占锁,安全写入并防止同时写入。操作完成后务必释放锁,以便其他进程能够访问。 - 在 Windows 上,请使用
msvcrt.locking()。如需要更高级的用法,可考虑使用portalocker或filelock等第三方库。
原子性文件写入模式
为防止文件在更新时损坏,应先写入临时文件,成功后用 os.replace 替换。
先写入临时文件再替换,可避免在崩溃时文件损坏。
1import os
2from pathlib import Path
3import tempfile
4
5def atomic_write(path: Path, data: str, encoding="utf-8"):
6 # Write to a temp file in the same directory and atomically replace.
7 dirpath = path.parent
8 with tempfile.NamedTemporaryFile(
9 mode="w",
10 encoding=encoding,
11 dir=str(dirpath),
12 delete=False
13 ) as tmp:
14 tmp.write(data)
15 tmp.flush()
16 tempname = tmp.name
17
18 # Atomic replacement (on most OSes)
19 os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')os.replace 能在同一文件系统内实现原子替换。注意,不同挂载点之间无法保证原子性。
通过 mmap 快速访问(适用于大数据量)
针对大型文件的随机访问,mmap 可提升 I/O 性能。主要用于二进制操作。
以下代码将文件映射到内存,并读写指定字节范围。更改文件大小时请格外注意。
1import mmap
2
3with open("data.bin", "r+b") as f:
4 mm = mmap.mmap(f.fileno(), 0) # map entire file
5 try:
6 print(mm[:20]) # first 20 bytes
7 mm[0:4] = b"\x00\x01\x02\x03" # modify bytes
8 finally:
9 mm.close()- 此代码用
mmap将二进制文件映射到内存,并进行字节级的读写操作。内存映射可以实现对大数据的快速随机访问。 mmap虽高效,但使用不当可能导致数据一致性问题。根据需要调用flush()进行同步。
CSV / JSON / Pickle:按格式读写
特定数据格式有专用模块。使用 csv 处理 CSV,json 处理 JSON,pickle 用于保存 Python 对象。
以下代码展示了 CSV 和 JSON 的基本读写及 Pickle 的使用示例。由于 Pickle 可能执行任意代码,请避免从不可信来源加载数据。
1import csv
2import json
3import pickle
4
5# CSV write
6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
7 writer = csv.writer(f)
8 writer.writerow(["name", "age"])
9 writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14 json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19 pickle.dump(obj, f)- 建议为 CSV 指定
newline="",以防止在 Windows 出现多余空行。设置ensure_ascii=False,可使 JSON 中的 UTF-8 字符保持可读。
直接读写压缩文件(gzip / bz2 / zipfile)
直接操作 gzip 或 zip 文件可以节省磁盘空间。标准库自带相关模块。
以下示例演示了用文本方式简单读写 gzip 文件。
1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5 f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9 print(f.read())- 根据压缩级别和格式,压缩率与速度之间存在权衡。
安全性和漏洞防护措施
以下几点可用于考虑安全性和漏洞防护措施。
- 严禁将不可信输入直接用于文件名或路径。
- 仅在可信来源下使用 Pickle。
- 最小化执行权限,仅给予文件操作的进程最少必要权限。
- 使用
tempfile管理临时文件,不要在公共目录存储明文文件。
如果在文件路径中使用用户输入,需进行规范化和验证。如,可用 Path.resolve() 并检查父目录。
1from pathlib import Path
2
3def safe_path(base_dir: Path, user_path: str) -> Path:
4 candidate = (base_dir / user_path).resolve()
5 if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
6 raise ValueError("Invalid path")
7 return candidate
8
9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')- 在 Web 应用或公开 API 中使用外部输入作为文件路径时尤需谨慎。
常用模式总结
- 始终使用
with语句(自动关闭文件)。 - 对文本数据显式指定
encoding。 - 分块读写大型文件。
- 为共享资源引入文件锁。
- 对于关键更新,请采用“写入临时文件 → os.replace”的原子操作模式。
- 在执行危险操作(如删除或覆盖)之前务必确认并做好备份。
- 将外部输入用作文件路径时请进行规范化和验证。
总结
进行文件操作时,采用安全可靠的技术方法很重要,比如使用with语句、明确指定编码和原子写入等。对于大规模处理或并发访问,有必要实施锁机制和日志管理,以防止数据损坏和冲突。兼顾效率与安全是实现可靠文件操作的关键。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。