跳至内容

Python文件处理

本篇介绍 Python 的文件读写操作,包括 open() 基础用法、文件模式、编码处理,以及现代推荐的 pathlib 路径操作。

文件操作基础

Python 通过 open() 函数打开文件,得到一个文件对象,然后通过文件对象进行读写操作。操作完毕后必须关闭文件以释放系统资源。

# 手动关闭(不推荐,容易遗忘)
f = open("data.txt", "r", encoding="utf-8")
content = f.read()
f.close()

# 推荐:with 语句自动关闭
with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)
# 离开 with 块时自动调用 f.close()

文件打开模式

模式含义
r只读(默认),文件不存在则报错
w只写,文件不存在则创建,存在则清空
a追加写,不存在则创建,存在则移动指针到末尾
x独占创建,文件已存在则报错(Python 3+)
r+可读可写,文件不存在则报错
b二进制模式(与 r/w/a 组合使用,如 rbwb
t文本模式(默认,与 b 互斥)
# 写入文件
with open("log.txt", "w", encoding="utf-8") as f:
    f.write("第一行\n")
    f.write("第二行\n")

# 追加写入
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("第三行\n")

# 同时打开两个文件(读入 → 写出)
with open("source.txt", "r", encoding="utf-8") as src, \
     open("dest.txt", "w", encoding="utf-8") as dst:
    dst.write(src.read())

读取方式

with open("data.txt", "r", encoding="utf-8") as f:
    # 一次性读取全部内容为字符串
    content = f.read()

    # 读取一行(包含末尾 \n)
    line = f.readline()

    # 读取所有行,返回列表
    lines = f.readlines()  # ['第一行\n', '第二行\n', ...]

# 推荐:逐行迭代(不会将全文读入内存,适合大文件)
with open("large.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip())  # rstrip() 去掉行末换行符

二进制模式

处理图片、视频、音频等非文本文件必须使用二进制模式(b),不能指定 encoding

# 复制二进制文件
with open("photo.jpg", "rb") as src, open("copy.jpg", "wb") as dst:
    while True:
        chunk = src.read(4096)   # 每次读 4 KB
        if not chunk:
            break
        dst.write(chunk)

# 更简洁的写法
import shutil
shutil.copyfile("photo.jpg", "copy.jpg")

编码处理

在 Windows 下,open() 不指定 encoding 时默认使用系统编码(gbk/cp936),可能导致乱码。始终显式指定 encoding="utf-8"
# 读取 UTF-8 文件
with open("utf8.txt", "r", encoding="utf-8") as f:
    print(f.read())

# 处理 GBK 编码的历史文件
with open("gbk.txt", "r", encoding="gbk") as f:
    content = f.read()

# 写入时指定编码
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("你好,世界!\n")

# 遇到无法解码的字节时的容错处理
with open("mixed.txt", "r", encoding="utf-8", errors="ignore") as f:
    content = f.read()   # 跳过无法解码的字节

# errors="replace":用 ? 替换无法解码的字节
with open("mixed.txt", "r", encoding="utf-8", errors="replace") as f:
    content = f.read()

文件指针控制

with open("data.txt", "rb") as f:
    # tell():返回当前指针位置(字节数)
    print(f.tell())    # 0

    f.read(5)
    print(f.tell())    # 5

    # seek(offset, whence)
    f.seek(0)          # 移到文件开头(等价于 f.seek(0, 0))
    f.seek(0, 2)       # 移到文件末尾(whence=2 表示以末尾为参照)
    print(f.tell())    # 文件总字节数

    f.seek(-3, 2)      # 从末尾往前移 3 字节
    print(f.read().decode("utf-8"))   # 最后 3 字节内容

pathlib — 现代路径操作

pathlib.Path 是 Python 3.4+ 推荐的路径操作方式,比 os.path 更直观:

from pathlib import Path

# 创建路径对象
data_dir = Path("data")
config = Path("/etc/app/config.yaml")

# 路径拼接(/ 运算符)
log_file = data_dir / "logs" / "app.log"

# 路径属性
p = Path("/home/user/docs/readme.txt")
print(p.name)     # readme.txt
print(p.stem)     # readme
print(p.suffix)   # .txt
print(p.parent)   # /home/user/docs

# 存在性检查
print(p.exists())   # 路径是否存在
print(p.is_file())  # 是否是文件
print(p.is_dir())   # 是否是目录

# 快捷读写(自动管理打开关闭)
output = Path("output.txt")
output.write_text("Hello, World!", encoding="utf-8")
text = output.read_text(encoding="utf-8")

# 创建目录(parents=True 递归创建)
Path("a/b/c").mkdir(parents=True, exist_ok=True)

# 删除
output.unlink(missing_ok=True)    # 删除文件
Path("empty_dir").rmdir()         # 删除空目录

# 遍历目录
for f in Path(".").iterdir():
    if f.is_file():
        print(f.name)

# glob 模式匹配
for py in Path("src").glob("**/*.py"):  # 递归查找
    print(py)

文件修改(两种方式)

文件内容不可直接修改(硬盘写入是覆盖机制),通常用以下两种方式实现"修改":

from pathlib import Path

# 方式一:全部读入内存,修改后写回(适合小文件)
path = Path("db.txt")
content = path.read_text(encoding="utf-8")
path.write_text(content.replace("old_value", "new_value"), encoding="utf-8")

# 方式二:逐行读取并写入临时文件(适合大文件)
import shutil, os

src = Path("db.txt")
tmp = Path(".db.txt.swap")

with src.open("r", encoding="utf-8") as r, \
     tmp.open("w", encoding="utf-8") as w:
    for line in r:
        w.write(line.replace("old_value", "new_value"))

tmp.replace(src)   # 原子替换(等价于 os.replace)

实战:监控日志新增行

import time
from pathlib import Path

def tail_log(path: Path) -> None:
    """类似 tail -f,实时输出日志新增行。"""
    with path.open("rb") as f:
        f.seek(0, 2)   # 跳到文件末尾
        while True:
            line = f.readline()
            if line:
                print(line.decode("utf-8"), end="")
            else:
                time.sleep(0.5)

# tail_log(Path("access.log"))
最后更新于