🐍 第08课:路径操作

—— pathlib:面向对象的路径管理

🏆 批量重命名100文件
✅ Python验证通过

📌 本课目标

1️⃣ Path 对象基础

from pathlib import Path

# 创建 Path 对象
p = Path("/home/user/docs/report.txt")
print(p)           # /home/user/docs/report.txt
print(type(p))     # <class 'pathlib.PosixPath'>

# 路径各部分
print(p.anchor)    # /
print(p.parent)    # /home/user/docs
print(p.name)      # report.txt
print(p.stem)      # report
print(p.suffix)    # .txt
print(p.suffixes)  # ['.txt']
print(p.parts)     # ('/', 'home', 'user', 'docs', 'report.txt')

# 当前目录与用户目录
print(Path.cwd())       # 当前工作目录
print(Path.home())      # 用户主目录

2️⃣ 路径拼接与分解

from pathlib import Path

# / 运算符拼接路径(pathlib 特色!)
base = Path("/home/user")
full = base / "docs" / "report.txt"
print(full)  # /home/user/docs/report.txt

# 也可以用 joinpath
full2 = base.joinpath("docs", "report.txt")

# 修改路径各部分
p = Path("report.txt")
print(p.with_suffix(".md"))     # report.md
print(p.with_name("summary.md"))  # summary.md
print(p.with_stem("final"))      # final.txt (Python 3.9+)

# 相对路径
p = Path("/home/user/docs/report.txt")
print(p.relative_to(Path("/home/user")))  # docs/report.txt

# resolve() 获取绝对路径(解析 .. 和 .)
print(Path("../docs/./report.txt").resolve())

3️⃣ 文件/目录查询

from pathlib import Path

p = Path("/home/user/docs/report.txt")

# 存在性检查
p.exists()        # 是否存在
p.is_file()       # 是否为文件
p.is_dir()        # 是否为目录
p.is_symlink()    # 是否为符号链接

# 文件属性
if p.exists():
    print(p.stat())           # os.stat_result 对象
    print(p.stat().st_size)   # 文件大小(字节)
    print(p.stat().st_mtime)  # 修改时间戳
    print(p.owner())          # 所有者
    print(p.group())          # 所属组
    print(p.chmod(0o755))      # 修改权限

4️⃣ 目录遍历

from pathlib import Path

# iterdir - 列出直接子项
for child in Path(".").iterdir():
    print(child.name)

# glob - 模式匹配
for py_file in Path(".").glob("*.py"):
    print(py_file)

# rglob - 递归匹配(子目录也搜索)
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# ** 通配符
for f in Path(".").glob("src/**/*.py"):
    print(f)

# 常用过滤
def find_large_files(directory, min_size_mb=10):
    """查找大于指定大小的文件"""
    dir_path = Path(directory)
    min_size = min_size_mb * 1024 * 1024
    for f in dir_path.rglob("*"):
        if f.is_file() and f.stat().st_size >= min_size:
            size_mb = f.stat().st_size / (1024 * 1024)
            yield f, round(size_mb, 2)

# 统计目录下各类文件数量
from collections import Counter
ext_counter = Counter(
    f.suffix for f in Path(".").rglob("*") if f.is_file()
)
for ext, count in ext_counter.most_common(10):
    print(f"{ext or '无扩展名'}: {count}")

5️⃣ 文件/目录创建与删除

from pathlib import Path

# 创建目录
Path("new_dir").mkdir()                    # 已存在会报错
Path("a/b/c").mkdir(parents=True, exist_ok=True)  # 递归创建,已存在不报错

# 创建文件
p = Path("hello.txt")
p.write_text("Hello, World!", encoding="utf-8")
p.write_bytes(b"Binary data")

# 读取文件
text = p.read_text(encoding="utf-8")
data = p.read_bytes()

# 追加内容
with p.open("a", encoding="utf-8") as f:
    f.write("\nAppended line")

# 删除
p.unlink()            # 删除文件
p.unlink(missing_ok=True)  # 不存在不报错(Python 3.8+)

# 删除目录
Path("empty_dir").rmdir()   # 只能删空目录
# 删除非空目录需要 shutil
import shutil
shutil.rmtree("non_empty_dir")

# 重命名/移动
p.rename("new_name.txt")
p.replace("existing.txt")  # 覆盖已存在的目标

6️⃣ 实战:批量重命名100文件

from pathlib import Path
import re
import tempfile

def batch_rename(directory, pattern, replacement, dry_run=True):
    """
    批量重命名文件
    pattern: 正则匹配模式
    replacement: 替换字符串
    dry_run: True=只预览不执行
    """
    dir_path = Path(directory)
    renamed = 0

    for f in sorted(dir_path.iterdir()):
        if not f.is_file():
            continue
        new_name = re.sub(pattern, replacement, f.name)
        if new_name != f.name:
            new_path = f.parent / new_name
            if dry_run:
                print(f"[预览] {f.name} → {new_name}")
            else:
                f.rename(new_path)
                print(f"[执行] {f.name} → {new_name}")
            renamed += 1

    print(f"\n共 {'预览' if dry_run else '重命名'} {renamed} 个文件")
    return renamed

# 示例1: 将空格替换为下划线
# batch_rename("./photos", r' ', r'_')

# 示例2: 文件名前加日期前缀
# batch_rename("./docs", r'^(.*)$', r'2026-05-19-\1')

# 示例3: 统一扩展名为小写
# batch_rename("./files", r'\.JPG$', r'.jpg')

# 创建100个测试文件并重命名
def demo_batch_rename():
    with tempfile.TemporaryDirectory() as tmpdir:
        dir_path = Path(tmpdir)

        # 创建100个测试文件
        for i in range(1, 101):
            (dir_path / f"IMG_{i:04d} .JPG").write_text("")

        # 预览重命名
        print("=== 预览 ===")
        count = batch_rename(dir_path, r' ', r'_', dry_run=True)
        assert count == 100

        # 执行重命名
        print("\n=== 执行 ===")
        count = batch_rename(dir_path, r' ', r'_', dry_run=False)

        # 验证
        files = list(dir_path.iterdir())
        assert all(' ' not in f.name for f in files)
        print("\n✅ 批量重命名验证通过")

7️⃣ 验证脚本

#!/usr/bin/env python3
"""第08课 路径操作验证"""
from pathlib import Path
import tempfile
import re

def test_path_parts():
    """路径分解测试"""
    p = Path("/home/user/docs/report.txt")
    assert p.name == "report.txt"
    assert p.stem == "report"
    assert p.suffix == ".txt"
    print("✅ 路径分解测试通过")

def test_path_join():
    """路径拼接测试"""
    base = Path("/home/user")
    full = base / "docs" / "report.txt"
    assert str(full) == "/home/user/docs/report.txt"
    assert full.with_suffix(".md") == Path("/home/user/docs/report.md")
    print("✅ 路径拼接测试通过")

def test_file_ops():
    """文件操作测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        p = Path(tmpdir) / "test.txt"
        p.write_text("Hello", encoding="utf-8")
        assert p.read_text(encoding="utf-8") == "Hello"
        assert p.is_file()
        assert p.stat().st_size == 5
        p.unlink()
        assert not p.exists()
    print("✅ 文件操作测试通过")

def test_dir_ops():
    """目录操作测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        base = Path(tmpdir)
        (base / "a" / "b").mkdir(parents=True)
        (base / "a" / "file.txt").write_text("test")
        assert (base / "a" / "b").is_dir()
        assert (base / "a" / "file.txt").is_file()
    print("✅ 目录操作测试通过")

def test_batch_rename():
    """批量重命名测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        dir_path = Path(tmpdir)
        # 创建测试文件
        for i in range(1, 101):
            (dir_path / f"file_{i:03d} .txt").write_text("")

        # 重命名:去空格
        for f in dir_path.iterdir():
            new_name = f.name.replace(" ", "_")
            f.rename(f.parent / new_name)

        # 验证
        files = list(dir_path.iterdir())
        assert len(files) == 100
        assert all(' ' not in f.name for f in files)
    print("✅ 批量重命名测试通过")

if __name__ == "__main__":
    test_path_parts()
    test_path_join()
    test_file_ops()
    test_dir_ops()
    test_batch_rename()
    print("\n🎉 第08课全部验证通过!")
✅ 路径分解测试通过 ✅ 路径拼接测试通过 ✅ 文件操作测试通过 ✅ 目录操作测试通过 ✅ 批量重命名测试通过 🎉 第08课全部验证通过!

🔑 本课要点

  1. Path > os.path——pathlib 是面向对象的,更 Pythonic
  2. / 运算符——路径拼接最优雅的方式
  3. rglob——递归搜索利器,取代 os.walk
  4. dry_run=True——批量操作前先预览,安全第一
  5. exist_ok=True——mkdir 不怕目录已存在