Python 基础教程:文件操作与路径处理

前言

Python 的文件操作能力是其”电池包含”理念的典型体现。无论是读取配置文件、处理日志文件,还是批量操作文件系统,Python 都提供了简洁而强大的 API。

本文将系统讲解:

  • 路径处理的新旧两代方案对比
  • 文件读写的标准操作与进阶技巧
  • 安全实践与异常处理
  • 常见场景的代码模板

路径处理:pathlib vs os.path

技术选型对比

特性pathlib.Pathos.path
面向对象✅ 是❌ 否
链式调用✅ 支持❌ 不支持
跨平台✅ 自动适配✅ 需手动处理
可读性⭐⭐⭐⭐⭐⭐⭐⭐
Python 版本3.4+所有版本

结论:优先使用 pathlib,它是 Python 3.4+ 的现代标准。

基础路径操作

from pathlib import Path
​
# 1. 创建路径对象
p = Path("/home/user/documents/report.txt")
​
# 2. 路径拼接(跨平台安全的 / 运算符)
docs = Path("/home/user") / "documents" / "report.txt"
# 等价于: os.path.join("/home/user", "documents", "report.txt")
​
# 3. 获取路径组成部分
print(p.name)        # "report.txt" — 文件名
print(p.stem)        # "report" — 不含扩展名
print(p.suffix)      # ".txt" — 扩展名
print(p.parent)      # Path("/home/user/documents") — 父目录
print(p.parent.parent)  # Path("/home/user")
​
# 4. 判断路径类型
print(p.is_file())   # True — 是否为文件
print(p.is_dir())    # False — 是否为目录
print(p.exists())    # True — 是否存在

路径信息提取

from pathlib import Path
​
p = Path("/home/user/documents/images/photo.jpg")
​
# 拆解路径
print(p.parts)        # ('/', 'home', 'user', 'documents', 'images', 'photo.jpg')
​
# 获取绝对路径
print(p.resolve())    # PosixPath('/home/user/documents/images/photo.jpg')
​
# 获取文件大小
print(p.stat().st_size)  # 字节数
​
# 获取修改时间
from datetime import datetime
mtime = datetime.fromtimestamp(p.stat().st_mtime)
print(mtime)  # 2026-05-15 10:30:45

目录遍历

from pathlib import Path
​
base = Path("/home/user/projects")
​
# 列出目录下所有文件(含子目录)
for item in base.rglob("*"):  # rglob 递归搜索
    print(item)
​
# 仅列出特定扩展名的文件
for py_file in base.rglob("*.py"):
    print(py_file)
​
# 排除特定目录
for item in base.rglob("*"):
    if ".git" not in item.parts:
        print(item)
​
# 列出直接子项(不递归)
for item in base.iterdir():
    print(item.name)

文件基础操作

读取文件

from pathlib import Path
​
# 方法1:read_text() — 一次性读取全部文本
content = Path("config.ini").read_text(encoding="utf-8")
​
# 方法2:read_bytes() — 读取二进制
data = Path("image.png").read_bytes()
​
# 方法3:逐行读取(大文件推荐)
for line in Path("large_log.txt").open(encoding="utf-8"):
    print(line.strip())

写入文件

from pathlib import Path
​
output = Path("output.txt")
​
# 方法1:write_text() — 写入文本(覆盖)
output.write_text("Hello, World!\n第二行", encoding="utf-8")
​
# 方法2:write_bytes() — 写入二进制
output.write_bytes(b"\x89PNG\r\n\x1a\n")
​
# 方法3:append_text() — 追加模式
output.append_text("\n追加的内容", encoding="utf-8")

文本编码注意事项

from pathlib import Path
​
# 最佳实践:始终指定编码
config = Path("config.yaml").read_text(encoding="utf-8")
​
# 处理未知编码(回退方案)
def safe_read(path, encodings=("utf-8", "gbk", "latin-1")):
    for enc in encodings:
        try:
            return Path(path).read_text(encoding=enc)
        except UnicodeDecodeError:
            continue
    raise ValueError(f"无法解码文件: {path}")
​
content = safe_read("mixed_encoding.txt")

高级文件操作

文件复制

import shutil
from pathlib import Path
​
src = Path("source.txt")
dst = Path("backup/source_copy.txt")
​
# 方法1:shutil.copy — 仅复制内容
shutil.copy(src, dst)
​
# 方法2:shutil.copy2 — 复制内容+元数据(时间戳等)
shutil.copy2(src, dst)
​
# 方法3:Path.copy() (Python 3.26+)
dst.parent.mkdir(parents=True, exist_ok=True)
src.copy(dst)
​
# 复制整个目录
shutil.copytree("source_dir", "backup_dir", dirs_exist_ok=True)

文件移动与重命名

import shutil
from pathlib import Path
​
# 移动文件
shutil.move("old_location.txt", "new_location.txt")
​
# 重命名
Path("old_name.txt").rename("new_name.txt")
​
# 安全重命名(目标存在则自动添加序号)
def safe_rename(src, dst):
    """目标文件存在时,自动命名为 file_1.txt, file_2.txt"""
    if not dst.exists():
        return src.rename(dst)
    
    stem = dst.stem
    suffix = dst.suffix
    parent = dst.parent
    
    for i in range(1, 1000):
        new_name = f"{stem}_{i}{suffix}"
        new_path = parent / new_name
        if not new_path.exists():
            return src.rename(new_path)
    
    raise FileExistsError("重命名次数超过上限")
​
safe_rename(Path("test.txt"), Path("output.txt"))
# 如果 output.txt 存在,则保存为 output_1.txt

文件删除

from pathlib import Path
​
# 删除单个文件
Path("temp.txt").unlink(missing_ok=True)  # missing_ok=True 避免文件不存在报错
​
# 删除空目录
Path("empty_dir").rmdir()
​
# 删除目录树(包含内容)
import shutil
shutil.rmtree("old_project", ignore_errors=True)

创建临时文件/目录

import tempfile
from pathlib import Path
​
# 临时文件
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=True) as f:
    f.write("临时数据")
    temp_path = f.name
​
# 临时目录
with tempfile.TemporaryDirectory() as tmpdir:
    temp_file = Path(tmpdir) / "data.json"
    temp_file.write_text('{"key": "value"}')
    # 使用完毕自动清理
​
# 自定义临时目录位置
temp_dir = Path(tempfile.gettempdir()) / "myapp_cache"
temp_dir.mkdir(exist_ok=True)

上下文管理器与资源安全

为什么必须使用 with 语句

# ❌ 错误示范:忘记关闭文件
def bad_read():
    f = open("large_file.txt", encoding="utf-8")
    data = f.read()  # 如果这里抛异常,文件永不关闭
    f.close()
    return data
​
# ✅ 正确做法:with 语句自动管理资源
def good_read():
    with open("large_file.txt", encoding="utf-8") as f:
        return f.read()  # 离开 with 块自动关闭文件
​
# ✅ pathlib 同样支持上下文管理器
from pathlib import Path
​
with Path("data.txt").open("r", encoding="utf-8") as f:
    lines = f.readlines()

二进制文件操作

from pathlib import Path
​
# 图片复制
src = Path("photo.jpg")
dst = Path("backup/photo_copy.jpg")
dst.parent.mkdir(parents=True, exist_ok=True)
​
with src.open("rb") as fsrc:
    with dst.open("wb") as fdst:
        # 分块复制(处理大文件)
        block_size = 8192
        while chunk := fsrc.read(block_size):
            fdst.write(chunk)
​
# 或者一行搞定
src.copy(dst)  # Python 3.26+

异常处理模板

from pathlib import Path
import logging
​
logger = logging.getLogger(__name__)
​
def safe_file_operation(src_path, dst_path):
    """安全的文件操作包装器"""
    src = Path(src_path)
    dst = Path(dst_path)
    
    # 前置检查
    if not src.exists():
        raise FileNotFoundError(f"源文件不存在: {src}")
    
    if src.is_dir():
        raise IsADirectoryError(f"源路径是目录而非文件: {src}")
    
    try:
        # 确保目标目录存在
        dst.parent.mkdir(parents=True, exist_ok=True)
        
        # 执行操作
        src.copy(dst)
        logger.info(f"文件复制成功: {src} -> {dst}")
        
    except PermissionError:
        logger.error(f"权限不足: {dst}")
        raise
    except OSError as e:
        logger.error(f"系统错误: {e}")
        raise
​
# 使用示例
try:
    safe_file_operation("input.txt", "output/result.txt")
except Exception as e:
    print(f"操作失败: {e}")

常见场景实战

场景1:批量重命名文件

from pathlib import Path
​
def batch_rename(directory, pattern, replacement):
    """批量重命名:替换文件名中的特定字符串"""
    dir_path = Path(directory)
    
    renamed = []
    for file in dir_path.iterdir():
        if pattern in file.name:
            new_name = file.name.replace(pattern, replacement)
            new_path = file.rename(file.parent / new_name)
            renamed.append((file.name, new_name))
    
    return renamed
​
# 示例:将所有 "IMG_" 替换为 "Photo_"
results = batch_rename("./photos", "IMG_", "Photo_")
for old, new in results:
    print(f"{old} -> {new}")

场景2:配置文件处理

from pathlib import Path
import json
​
class ConfigManager:
    """配置文件管理器"""
    
    def __init__(self, config_dir=None):
        self.config_dir = Path(config_dir) if config_dir else Path.home() / ".myapp"
        self.config_dir.mkdir(parents=True, exist_ok=True)
        self.config_file = self.config_dir / "config.json"
    
    def load(self):
        """加载配置,带默认值"""
        if self.config_file.exists():
            return json.loads(self.config_file.read_text(encoding="utf-8"))
        return {"theme": "light", "language": "zh-CN"}
    
    def save(self, config):
        """保存配置"""
        self.config_file.write_text(
            json.dumps(config, indent=2, ensure_ascii=False),
            encoding="utf-8"
        )
​
# 使用
config = ConfigManager()
settings = config.load()
settings["theme"] = "dark"
config.save(settings)

场景3:日志文件轮转

from pathlib import Path
from datetime import datetime
import gzip
import shutil
​
def rotate_log(log_path, max_size_mb=10, keep_count=5):
    """日志轮转:超过大小则压缩归档"""
    log = Path(log_path)
    
    if not log.exists():
        return
    
    size_mb = log.stat().st_size / (1024 * 1024)
    
    if size_mb < max_size_mb:
        return
    
    # 生成带时间戳的归档文件名
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    archive = log.parent / f"{log.stem}_{timestamp}.log.gz"
    
    # 压缩并创建新日志
    with log.open("rb") as f_in:
        with gzip.open(archive, "wb") as f_out:
            shutil.copyfileobj(f_in, f_out)
    
    # 清空原日志
    log.write_text("", encoding="utf-8")
    
    # 清理旧归档
    pattern = f"{log.stem}_*.log.gz"
    archives = sorted(log.parent.glob(pattern), key=lambda p: p.stat().st_ctime)
    for old in archives[:-keep_count]:
        old.unlink()
​
# 使用
rotate_log("app.log", max_size_mb=10, keep_count=5)

场景4:递归搜索与过滤

from pathlib import Path
from datetime import datetime, timedelta
​
def find_files(directory, extensions=None, modified_after=None, min_size=None):
    """
    高级文件搜索
    
    Args:
        directory: 搜索目录
        extensions: 文件扩展名列表,如 ['.py', '.txt']
        modified_after: 仅返回修改时间在指定日期之后的文件
        min_size: 仅返回大于指定大小的文件(字节)
    """
    dir_path = Path(directory)
    results = []
    
    for file in dir_path.rglob("*"):
        if not file.is_file():
            continue
        
        # 扩展名过滤
        if extensions and file.suffix.lower() not in extensions:
            continue
        
        # 时间过滤
        if modified_after:
            mtime = datetime.fromtimestamp(file.stat().st_mtime)
            if mtime < modified_after:
                continue
        
        # 大小过滤
        if min_size and file.stat().st_size < min_size:
            continue
        
        results.append(file)
    
    return sorted(results)
​
# 示例:查找近7天修改的 Python 文件
one_week_ago = datetime.now() - timedelta(days=7)
recent_files = find_files(
    "./projects",
    extensions=[".py"],
    modified_after=one_week_ago
)
​
for f in recent_files:
    size_kb = f.stat().st_size / 1024
    print(f"{f} ({size_kb:.1f} KB)")

最佳实践

安全规范清单

规范说明代码示例
✅ 始终指定编码避免跨平台乱码read_text(encoding="utf-8")
✅ 使用绝对路径避免相对路径歧义Path.resolve()
✅ 创建目录前检查避免覆盖已有文件mkdir(parents=True, exist_ok=True)
✅ 异常处理防止程序崩溃try/except 包裹关键操作
✅ 使用 with 语句自动释放资源with open(...) as f:
❌ 避免字符串拼接路径不同系统分隔符不同Path / "dir" 替代

性能优化技巧

from pathlib import Path
​
# 1. 大文件分块读写
def read_large_file(path, chunk_size=65536):
    with open(path, "rb") as f:
        while chunk := f.read(chunk_size):
            yield chunk
​
# 2. 批量操作减少系统调用
from pathlib import Path
​
def bulk_copy(files, target_dir):
    """批量复制文件,减少目录切换开销"""
    target = Path(target_dir)
    target.mkdir(parents=True, exist_ok=True)
    
    for src in files:
        src = Path(src)
        if src.is_file():
            src.copy(target / src.name)  # Python 3.26+
​
# 3. 使用 glob 替代 listdir + filter
# ❌ 低效
files = [f for f in Path(".").iterdir() if f.suffix == ".py"]
​
# ✅ 高效
files = list(Path(".").glob("*.py"))

跨平台兼容

from pathlib import Path
import os
​
# pathlib 自动处理路径分隔符
# Linux/Mac: /home/user/file.txt
# Windows: C:\Users\user\file.txt
​
# 检测操作系统
HOME = Path.home()
TEMP = Path(os.environ.get("TEMP", "/tmp"))
​
# 使用环境变量
USER_DATA = Path(os.environ.get("APPDATA" if os.name == "nt" else "HOME")) / "MyApp"
USER_DATA.mkdir(parents=True, exist_ok=True)

排错指南

常见错误与解决方案

错误1:FileNotFoundError

# ❌ 常见错误
content = Path("data/config.txt").read_text()
​
# ✅ 解决方案1:确保目录存在
Path("data/config.txt").parent.mkdir(parents=True, exist_ok=True)
​
# ✅ 解决方案2:检查文件是否存在
config_path = Path("data/config.txt")
if config_path.exists():
    content = config_path.read_text()
else:
    content = "{}"  # 使用默认空配置
​
# ✅ 解决方案3:使用默认值
content = Path("data/config.txt").read_text(encoding="utf-8") if Path("data/config.txt").exists() else "{}"

错误2:PermissionError

# 问题:文件被占用或无权限
# 解决方案1:关闭所有文件句柄
import gc
gc.collect()  # 强制垃圾回收释放文件句柄
​
# 解决方案2:检查文件属性
from pathlib import Path
import stat
​
p = Path("locked_file.txt")
if not p.stat().st_mode & stat.S_IWRITE:
    print("文件是只读的")
​
# 解决方案3:Windows 下检查进程
import subprocess
result = subprocess.run(
    ["handle", "locked_file.txt"],
    capture_output=True, text=True
)

错误3:IsADirectoryError / FileExistsError

from pathlib import Path
​
# 场景:目标路径是已存在的目录
src = Path("my_folder")
dst = Path("existing_folder")
​
# 解决方案1:shutil.copytree
import shutil
shutil.copytree(src, dst / src.name)  # 在目标目录下创建子目录
​
# 场景:想覆盖但 rename() 不允许
# rename() 在目标存在时会报错
​
# 解决方案2:先删除后重命名
if dst.exists():
    if dst.is_dir():
        shutil.rmtree(dst)
    else:
        dst.unlink()
src.rename(dst)

错误4:路径中文字符问题

from pathlib import Path
​
# Windows 中文路径问题
# 解决方案1:使用原始字符串
path = Path(r"C:\用户\管理员\文档")
​
# 解决方案2:Path 对象确保编码正确
path = Path("C:/用户/管理员/文档")  # 正斜杠在 Windows 也有效
​
# 解决方案3:统一编码
import sys
sys.stdout.reconfigure(encoding='utf-8')

错误5:编码检测与乱码

from pathlib import Path
import chardet
​
def detect_and_read(path):
    """自动检测文件编码并读取"""
    raw = Path(path).read_bytes()
    result = chardet.detect(raw)
    encoding = result["encoding"]
    
    # 常用编码映射
    encoding_map = {
        "GB2312": "gbk",
        "Windows-1252": "cp1252",
    }
    encoding = encoding_map.get(encoding, encoding or "utf-8")
    
    return raw.decode(encoding)
​
# 安装 chardet: pip install chardet
content = detect_and_read("chinese_text.txt")

调试技巧

from pathlib import Path
​
# 1. 打印完整路径信息
p = Path("relative/path.txt")
print(f"""
    绝对路径: {p.absolute()}
    解析路径: {p.resolve()}
    规范路径: {p.resolve().as_posix()}
    是否存在: {p.exists()}
    是否文件: {p.is_file()}
    是否目录: {p.is_dir()}
""")
​
# 2. 递归查看目录树
def tree(path, prefix="", max_depth=3, _current_depth=0):
    """可视化目录树"""
    if _current_depth >= max_depth:
        return
    
    path = Path(path)
    if not path.exists():
        return
    
    # 当前节点
    if path.is_dir():
        print(f"{prefix}📁 {path.name}/")
        new_prefix = prefix + "│   "
        for child in sorted(path.iterdir()):
            tree(child, new_prefix, max_depth, _current_depth + 1)
    else:
        size = path.stat().st_size
        print(f"{prefix}📄 {path.name} ({size} bytes)")
​
tree("./projects", max_depth=2)

总结

核心要点

  1. 优先使用 pathlib: 现代、链式、跨平台
  2. 始终指定编码: encoding="utf-8" 是最佳实践
  3. 使用 with 语句: 自动资源管理,防止泄漏
  4. 检查再操作: exists()is_file() 等判断方法
  5. 异常处理: 关键操作包裹 try/except

一图总结

┌─────────────────────────────────────────────────────────┐
│                    文件操作速查                          │
├─────────────────┬─────────────────┬─────────────────────┤
│     操作        │     pathlib     │       os/shutil     │
├─────────────────┼─────────────────┼─────────────────────┤
│ 读取文本        │ read_text()     │ open().read()       │
│ 写入文本        │ write_text()    │ open().write()      │
│ 复制文件        │ Path.copy()     │ shutil.copy()       │
│ 移动文件        │ Path.rename()    │ shutil.move()       │
│ 删除文件        │ Path.unlink()   │ os.remove()         │
│ 创建目录        │ mkdir()         │ os.makedirs()       │
│ 遍历文件        │ glob() / rglob()│ os.walk()           │
│ 检查存在        │ exists()        │ os.path.exists()    │
└─────────────────┴─────────────────┴─────────────────────┘

💡 提示: 本文代码适用于 Python 3.10+,部分高级特性(如 Path.copy())需要 Python 3.26+。 📚 扩展阅读:


原创内容,版权所有。未经授权,禁止转载。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容