☰
Python文件与目录操作三剑客:os、pathlib、shutil实战指南
2026/10/11 4:28:43 网站建设 项目流程

在写自动化脚本这条路子上折腾几年后,你会发现一个很有趣的现象:很多看起来花里胡哨的功能,底层翻来覆去就那么几个库。日常操作文件和目录这件事,更是绕不开Python标准库里的三剑客——os、pathlib和shutil。今天我想好好聊聊这三个模块,不只是罗列几个API,而是把它们在实际项目中该怎么用、用哪个、怎么避开常见的坑,一次讲清楚。

这个主题适合谁?刚入门Python、想用脚本替代手动文件整理的人;写了几年代码但一直在字符串拼接路径、见到路径就头疼的开发者;还有需要做批量处理、数据备份、日志清理这类常见运维任务的工程师。只要你的需求是“读写文件、遍历目录、拷贝移动、打包压缩”,这三个库掌握了,你会发现自己写这类脚本的效率能翻一倍,而且代码读起来极其舒服。

1. 整体设计与思路解构:三个库的分工逻辑

1.1 os / pathlib / shutil,它们各自管什么

很多人一上来就把这三个库混在一起查文档,查到一个能用的就开始写,结果代码风格乱糟糟的。其实从设计定位上看,这三个库的边界非常清晰:

  • os 模块是“操作系统接口”,负责调用系统层面的能力,比如进程环境、文件描述符、权限、环境变量、系统命令,其中最常用的是 os.path 子模块(路径字符串处理)和 os.walk / os.listdir / os.mkdir 这类目录操作函数。它的特点是功能最底层、最零散,但也是另外两个库的基石。
  • pathlib 是 Python 3.4 引入的“面向对象路径库”,它把路径抽象成 Path 对象,提供统一的、跨平台的路径处理方式。相比 os.path 全部用字符串函数,pathlib 允许你用对象方法、运算符重载(比如 / 操作符连接路径)去操作路径,代码语义更清晰,也是新项目里我优先推荐的使用方式。
  • shutil 是“高级文件操作工具箱”,专注于复制、移动、删除目录树、打包压缩这类“一次搞定一大片”的需求。它内部大量使用 os 模块,但把繁琐的细节封装好了,比如递归复制目录、递归删除非空目录。

用一句话概括:os 负责“能用”,pathlib 负责“好用”,shutil 负责“省事”。搞清楚这个分工,选型就不会迷茫。

1.2 选型逻辑:为什么新代码优先用 pathlib

我在早期写脚本的时候,一直是 os.path.join 配合字符串的忠实用户,代码大概是这种画风:

import os base_dir = "/data/projects" sub_dir = "2024" file_name = "report.txt" full_path = os.path.join(base_dir, sub_dir, file_name) print(full_path)

用起来倒也不算难受,但问题在于:字符串路径没有类型约束,你传给函数的是一个纯字符串,IDE 帮不了你,拼错一个斜杠方向、少一个分隔符,都要运行时报错才能发现。更麻烦的是,Windows 用反斜杠、Linux/macOS 用正斜杠,如果代码里硬编码路径分隔符,同一段代码在不同平台就会炸。

pathlib 解决的正是这个问题。它让你用一个 Path 对象承载路径,对象内部自己处理平台差异。同样的逻辑用 pathlib 写成:

from pathlib import Path base_dir = Path("/data/projects") sub_dir = "2024" # 这里甚至可以是普通字符串 file_name = "report.txt" full_path = base_dir / sub_dir / file_name print(full_path)

/ 运算符会把你传进去的零件自动拼成一个 Path 对象,Windows 上它输出反斜杠形式,Linux 上输出正斜杠形式。而且 Path 对象自带 .exists()、.is_file()、.suffix、.stem 这些属性方法,写代码时按一个点就能弹出候选列表,IDE 提示非常友好,出错概率直线下降。

那 os 是不是就该淘汰了?并不是。如果你要操作环境变量、设置文件权限、获取系统级的进程信息,或者调用某些底层接口(比如打开文件描述符、发送系统信号),pathlib 并没有完全覆盖,还是得回到 os。实际项目中,我的习惯是“路径表示用 pathlib,系统底层功能用 os”,两者混用并不冲突,pathlib 还提供了和 os 的转换接口(比如 os.fspath() 可以把 Path 转成字符串)。

1.3 从“函数式字符串操作”到“面向对象路径操作”的思维转变

用 pathlib 最大的障碍不在技术难度,而在思维惯性。字符串路径时代,你要思考的是“这个字符串怎么拼接、怎么裁剪”;Path 对象时代,你要思考的是“这个路径对象是什么、有哪些能力”。

举几个常见的对比:

字符串风格:

p = "/a/b/c.txt" base = os.path.basename(p) # 'c.txt' ext = os.path.splitext(p)[1] # '.txt' folder = os.path.dirname(p) # '/a/b'

Path 风格:

p = Path("/a/b/c.txt") p.name # 'c.txt' p.suffix # '.txt' p.stem # 'c' p.parent # /a/b

一眼就能看出差别:Path 的语义是“路径对象描述自身的属性”,读代码的人不需要去记忆 basename 的参数是拼接字符串还是原始路径,.name 这种属性名天然就能被猜出来。

这一点在重构老项目时体会特别深。原来 os.path 系列函数在逻辑复杂时容易写出“a = os.path.join(base, sub); b = os.path.abspath(a); c = os.path.dirname(b)”这种绕圈子的代码。而用 Path 的话,你只需要建立路径对象,然后通过属性一步步取数据,每一步的含义都清清楚楚,几乎不用写注释。

1.4 异常处理与操作安全的设计原则

文件系统操作最忌讳“假设它一定成功”。网络存储可能断开、磁盘可能满、文件可能正被另一个进程占用,任何情况都可能让脚本在中间某一步挂掉。设计的时候一定要把异常处理考虑进去,而不是等运行时报错之后再去改。

Python 里这些库抛出的异常类型有规律:路径不存在通常抛 FileNotFoundError,权限不足抛 PermissionError,目标已存在且不能覆盖时抛 FileExistsError,这三种是最常见的。更稳妥的做法是:用 pathlib 的 exists() 做提前判断,用 try/except 兜底真正的执行异常。很多初学者会纠结“要不要先检查再操作”,我的建议是——对不敏感的操作就直接执行然后捕获异常,避免检查完到执行期间文件状态发生变化(TOCTOU 问题);对于重要删除、覆盖类操作,则一定要做显式的确认或 dry-run 模拟,这个思路在后面实操案例里会重点体现。

2. 核心细节解析与实践要点

2.1 os 模块核心功能:最底层却也最零碎

os 模块的内容真的杂,日常文件操作主要会用到这几类:

  • 目录导航:os.getcwd() 返回当前目录字符串,os.chdir(path) 切换工作目录。
  • 目录遍历:os.listdir(path) 返回指定目录下的条目列表,os.walk(top) 以生成器方式递归遍历目录树,每轮返回 (当前目录, 子目录列表, 文件列表)。
  • 创建与删除:os.mkdir(path) 只创建一级目录,父目录不存在会报错;os.makedirs(path, exist_ok=True) 可以递归创建多级目录,exist_ok=True 是现在最推荐的写法,目标已存在也不会抛错。删除时 os.rmdir 只能删空目录,os.remove 只能删文件。
  • 重命名:os.rename(src, dst) 可以用于文件和目录。
  • 路径处理:os.path.join、os.path.exists、os.path.isfile、os.path.isdir、os.path.basename、os.path.dirname、os.path.splitext、os.path.getsize、os.path.getmtime 等。

特别注意几个细节点。os.walk 默认是自顶向下遍历,如果遍历过程中你还要修改目录结构(比如在遍历时删除文件),建议设置 topdown=True 并且动态修改 dirs 列表来控制递归;否则很容易触发“边遍历边删除导致目录不存在”的 RuntimeError。os.rename 在跨文件系统时会失败,如果源和目标在不同磁盘分区,应该用 shutil.move。

2.2 pathlib:日常路径操作的首选方案

pathlib 的核心是 Path 类,它派生出 PosixPath 和 WindowsPath 两个子类,你在不同平台实例化 Path 时,会自动得到对应平台的路径类。继承体系让路径天然具备平台感知能力,这是字符串方案无法比拟的。

常见的操作方法可以说是“麻雀虽小,五脏俱全”:

from pathlib import Path p = Path("data/reports/2025/final.txt") # 取各部分 print(p.parent) # data/reports/2025 print(p.parents) # 一个可迭代对象,逐级向上 print(p.name) # final.txt print(p.stem) # final print(p.suffix) # .txt # 判断类型 print(p.exists()) # 是否存在 print(p.is_file()) # 是否文件 print(p.is_dir()) # 是否目录 # 遍历目录 for item in Path("data").iterdir(): # 惰性迭代目录 print(item) # 通配符匹配 for py in Path("data").rglob("*.py"): # 递归找所有py文件 print(py) # 创建目录 Path("data/new/2025").mkdir(parents=True, exist_ok=True) # 删除文件(对象化操作相当于os.remove) Path("data/temp.txt").unlink(missing_ok=True) # missing_ok=True是3.8+特性

这里面最香的操作我认为是 rglob。以前想找一个目录下所有某类文件,得 os.walk 然后层层判断;现在一句话.rglob("*.jpg")就搞定。而且它返回的是惰性生成器,文件再多内存也不怕炸。

比较隐蔽的点是 Path("/").resolve() 会把相对路径转成绝对路径,并且会真实解析符号链接(symlink),这在需要拿到物理路径时很有用。但注意 resolve 默认要求路径存在,若路径不存在,在 Python 3.8 以下会抛异常,3.8 以上增加 strict=False 参数可以关闭检查。跨版本写代码时最好显式指定。

2.3 shutil:复制、移动、打包的“一站式商店”

shutil 里的常用函数,每一个都值得单独练熟:

  • shutil.copy(src, dst):只拷贝文件内容,返回目标路径字符串。注意它不保留文件元数据(如修改时间、权限)。
  • shutil.copy2(src, dst):拷贝内容的同时尽量保留元数据(权限、修改时间等),适合做备份。
  • shutil.copyfile(src, dst):只拷贝内容,不关心目标路径是不是目录,要求 dst 必须是完整文件路径。
  • shutil.copytree(src, dst, ignore=...):递归复制整个目录树。ignore 参数很重要,可以传一个忽略函数,比如用 shutil.ignore_patterns("pycache", "*.tmp") 来排除指定文件。
  • shutil.move(src, dst):移动文件或目录,自动处理跨文件系统的情况。
  • shutil.rmtree(path):递归删除目录树(包括非空目录)。这个函数危险性极高,用了它目录就没了,没有回收站可进,务必谨慎。
  • shutil.disk_usage(path):返回该路径所在磁盘的总容量、已用容量、可用容量(byte)。
  • shutil.make_archive(base_name, format, root_dir):打包压缩,format 支持 zip、tar 等。这个比手动调 zipfile/tarfile 要省心太多。

copy 和 copy2 的区别,是我见过很多人踩坑的地方。有的场景不关心文件修改时间(比如只是部署静态文件副本),用 copy 就够;但做增量备份如果依赖 mtime 判断文件是否变化,却用了 copy,会发现所有拷贝出来的新文件 mtime 全部变成了当前时间,导致备份策略混乱。正确选择 copy2 保留原始时间戳,备份脚本才能按时间维度准确判断。

2.4 协作与编码:别在细节上翻船

这三个库虽然是标准库,实际协作时也有一些编码层面的细节需要注意。

第一个是编码问题。读写文件时 open() 的 encoding 参数如果不显式指定,会依赖系统默认编码。Windows 下默认可能是 gbk,而文件内容实际是 UTF-8,一旦读取就 UnicodeDecodeError;Linux 下情况好一点但同样不是 100% 保险。我的习惯是:凡是读写文本,必须写 encoding="utf-8"(或者按文件实际编码指定),再怎么强调都不为过。

第二个是路径内部的特殊字符。文件名含空格、中文、括号其实没问题,因为 pathlib 和 os 都能处理;真正危险的是文件名为空字符串、路径分量中有连续分隔符等极端情况,以及 Windows 下路径末尾的空格或点号会被系统自动去除,如果你依赖字符串精确比较路径,会莫名其妙失败。

第三个是 Windows 的路径长度限制。经典 260 字符路径上限在 Windows 10 较新版本中可以通过组策略打开长路径支持,但就算支持,也会潜在地影响跨平台脚本。应对策略:尽量保持目录结构扁平、不要在脚本里粘死深层的绝对路径,用相对路径 + 当前工作目录组织逻辑。

3. 实操过程与核心环节实现

3.1 案例一:自动整理“下载”目录的按类型分类脚本

这个需求太经典了,基本上每个用电脑多的人都需要一个“把下载文件夹里的乱东西归类”的脚本。实现思路非常朴素:扫描目标目录,读取每个文件的扩展名,映射到分类标签(图片、文档、视频、压缩包),然后创建对应子目录,把文件移动过去。

完整的 pathlib 实现:

from pathlib import Path EXT_MAP = { (".png", ".jpg", ".jpeg", ".gif", ".webp"): "images", (".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md"): "documents", (".mp4", ".mkv", ".avi", ".mov"): "videos", (".zip", ".tar", ".gz", ".rar", ".7z"): "archives", (".mp3", ".wav", ".flac"): "audio", } def classify(file_path: Path) -> str: suffix = file_path.suffix.lower() for exts, category in EXT_MAP.items(): if suffix in exts: return category return "others" def organize(download_dir: str, dry_run: bool = True) -> None: base = Path(download_dir) if not base.is_dir(): raise NotADirectoryError(f"{base} 不是有效目录") for item in base.iterdir(): if item.is_dir(): continue # 跳过子目录,避免移动自己 category = classify(item) target_dir = base / category target_path = target_dir / item.name if dry_run: print(f"[模拟] 将 {item.name} 移动到 {target_path}") else: target_dir.mkdir(parents=True, exist_ok=True) # 目标已存在时添加不覆盖策略 if target_path.exists(): counter = 1 while True: new_name = f"{item.stem}_{counter}{item.suffix}" if not (target_dir / new_name).exists(): target_path = target_dir / new_name break counter += 1 item.rename(target_path) print(f"[完成] {item.name} -> {target_path}") if __name__ == "__main__": organize("/home/某开发者/Download", dry_run=True)

代码里几个设计点值得解释:

  • 用元组作为字典的键来映射扩展名,比一堆 if-elif 清爽得多,后面要加新分类只需扩展字典。
  • iterdir() 是惰性迭代器,比 os.listdir 更适合文件极多的目录。
  • dry_run 参数是给脚本上保险的最佳实践。第一次跑用 dry_run=True 只打印,检查移动目标是否符合预期,确认无误后再改成 dry_run=False 真正执行。这个“先模拟后执行”的思路在自动化脚本里可以推广到所有破坏性操作。
  • 遇到重名文件不覆盖,而是自动加 _1、_2 后缀,避免数据丢失。这种细节往往决定脚本能不能长久用下去。

3.2 案例二:增量备份工具(按时间/忽略规则筛选)

备份是另一个高频需求。我要备份的是一个项目工作目录,里面有源码、文档、临时产物,只需要把源码和文档复制出去,排除pycache、node_modules、.git 这类大目录或临时目录。

shutil.copytree 的 ignore 参数在这里就派上了大用场:

from pathlib import Path import shutil def backup_project(src_dir: str, dest_dir: str) -> None: src = Path(src_dir) dest = Path(dest_dir) if not src.is_dir(): raise NotADirectoryError("源目录不存在") # 复制整个目录树,但排除指定模式 shutil.copytree( src, dest / src.name, ignore=shutil.ignore_patterns( "__pycache__", "*.pyc", "node_modules", ".git", ".DS_Store", "*.tmp", ), dirs_exist_ok=True, # 3.8+,允许目标目录已存在并继续合并 ) print(f"备份完成:{src} -> {dest / src.name}") # 顺便打印磁盘占用情况 usage = shutil.disk_usage(dest) print(f"目标磁盘可用空间:{usage.free / (1024 ** 3):.2f} GB") if __name__ == "__main__": backup_project("/home/某开发者/projects/某跨平台系统", "/backup")

几个要点:

  • ignore_patterns 接受通配符模式字符串,模式会匹配路径组件的基名,所以pycache目录、任意位置的 .pyc 文件都会被排除。
  • dirs_exist_ok=True 是增量合并的关键参数,没有它,如果目标目录已存在,copytree 会直接报 FileExistsError。有了这个参数,第二次备份时会把新文件合并进已有备份目录,但注意:它不会删除目标目录中源目录已删除的文件。如果需要“镜像同步”效果(目标完全等同源),得额外遍历删除,或者换成调用系统 rsync 之类的外部工具。
  • make_archive 一键打包:如果想在备份后直接生成 zip 压缩包,shutil.make_archive("backup_20250101", "zip", root_dir="/home/某开发者/projects/某跨平台系统") 就能完成。它的 root_dir 参数非常关键,决定了压缩包内是包含源目录外层还是只包含内部文件,刚开始用容易搞混:root_dir 是压缩的根目录,base_dir(可选)是相对于 root_dir 的起点。如果只想压缩某跨平台系统目录的 contents,就设 root_dir 为该目录的父目录、base_dir 为该目录名,出来的压缩包打开就是某个目录包着内容的感觉。

3.3 案例三:清理过期临时文件(按修改时间删除)

日志和临时文件会越攒越多,磁盘告警几乎是运维的日常。写一个按 mtime 判断“超过 N 天未修改就删除”的清理脚本,正好把 os 和 pathlib 结合起来用:

from pathlib import Path import time import os def clean_old_files(root_dir: str, max_days: int, pattern: str = "*", dry_run: bool = True) -> None: root = Path(root_dir) if not root.is_dir(): raise NotADirectoryError("目录不存在") cutoff = time.time() - max_days * 24 * 3600 deleted_count = 0 for file_path in root.rglob(pattern): if not file_path.is_file(): continue mtime = file_path.stat().st_mtime # 最后修改时间,秒为单位 if mtime < cutoff: deleted_count += 1 size_mb = file_path.stat().st_size / (1024 * 1024) # 转为MB if dry_run: print(f"[模拟删除] {file_path} (修改时间: {time.ctime(mtime)}, 大小: {size_mb:.1f}MB)") else: # 防御性检查:确认它仍然存在且是文件,再删除 if file_path.is_file(): file_path.unlink(missing_ok=True) print(f"[已删除] {file_path}") print(f"本次扫描共删除 {deleted_count} 个过期文件({max_days} 天前)") if __name__ == "__main__": clean_old_files("/var/log/app", max_days=30, pattern="*.log", dry_run=True)

这个脚本里有几个经验和坑:

  • root.rglob(pattern) 在遍历时可能会因为权限不足访问不了某个子目录而抛 PermissionError。稳妥做法是外层加 try/except 或者在进入每级目录前判断是否有读权限。实际场景中,我会把“跳过无法访问的目录”作为默认行为,只统计失败次数,而不是让整个脚本中断。
  • 删除前用 is_file() 二次确认,防止“目录在遍历后被替换为同名单文件”这种极端情况。虽然概率低,但就这么一个小检查,能避免一些无法解释的灾难。
  • 时间判断用 st_mtime,如果想看“创建时间”,Windows 上可以用 st_ctime(Windows 上 ctime 是创建时间,Unix 上 ctime 是 inode 变更时间),跨平台代码建议别直接依赖 ctime,除非你有明确平台假设。

3.4 封装成命令行小工具:让脚本真正可用

上面的脚本写完后,很多人直接用 IDE 运行,改参数还要改代码,这样用起来很别扭。我会建议花一点时间,把参数暴露成命令行参数。用 argparse 标准库,很短就能实现,比如让用户通过python clean.py --root /var/log/app --days 30 --pattern "*.log" --confirm来控制清理。设计上默认 dry_run,只有传了 --confirm 才真正删除。这个习惯能让你把脚本从“自己临时用”升级成“可交给别人用”,省去很多解释成本。

4. 常见问题与排查技巧实录

4.1 Windows 下路径总是反斜杠,拼接脚本会炸吗?

经常有人问:在 Windows 写脚本用 os.path.join,传"a/b"和传"a\\b"有区别吗?答案是:os.path.join 和 pathlib 都会自动调用系统约定的分隔符来规范化输出,但你自己硬编码的"/"字符串经过 os.path.join 有时会不被当成分隔符。最坑的是 Linux 脚本拷到 Windows,代码里硬编码了"C:/data"这种路径,实际上对很多库来说正斜杠在 Windows 也能识别,但如果你把路径当作字符串去 split("/") 或比较,那就各种不匹配。

我的经验是:全项目统一用 pathlib 构造路径,不要手工拼分隔符;如果一定要和外部字符串做拼接,先 Path(str) 转换。保持“遇到路径就构造 Path”的肌肉记忆,这类问题基本绝迹。

4.2 中文文件名读取乱码 / UnicodeDecodeError

问题场景:文件名或目录名是中文,控制台打印正常,但打开文件时报错。这通常不是路径问题,而是系统默认编码问题。Windows 的 Python 3 默认 open 用 cp936(GBK)写文本,Linux 默认 UTF-8。文件如果本来就是 UTF-8,在 Windows 下用默认 encoding 去 open 就会 UnicodeDecodeError。

解决办法很简单:open 文件时显式传 encoding:

with open(path, "r", encoding="utf-8") as f: content = f.read()

但如果文件不是 UTF-8,请你先搞清楚它实际是什么编码。可以在编辑器里查看编码,或者用 chardet 库探测。写自动化脚本时,我习惯把“尝试 UTF-8,失败则 GBK”兜底逻辑放在工具函数里,这样至少不会在脚本中途因为一个编码问题直接崩掉。

4.3 shutil.rmtree 误删了整个目录,能恢复吗?

不能。shutil.rmtree 是直接删除,不进回收站。我早期犯过这个错:备份脚本里条件判断写反了,把“备份源目录”写成了“删除源目录”,还好是用在测试环境,教训不深,但在生产环境这种错误是不可逆的。

所以我现在有两条纪律:

  1. 所有 rmtree 调用,前面必须加一层显式确认(input 二次确认,或命令行参数 --force 才执行,或 dry_run 先行)。
  2. 删除逻辑之前打印出即将删除的路径清单,停止脚本后由人审核一遍。

这听起来笨拙,但自动化脚本的第一原则是“不要破坏数据”。哪怕因此多几十行代码,也是值得的。

4.4 os.walk 边遍历边删除,触发 RuntimeError

如果你在 os.walk 回调里对目录执行了 shutil.rmtree,很容易遇到“directory changed during iteration”或者 RuntimeError: generator raised StopIteration。os.walk 内部维护了一个扫描中的目录列表,你在遍历时把目录删了,它接着访问时就会崩溃。

解决方案有三种:

  • 先收集文件路径列表,遍历结束后再统一删除。
  • 用 topdown=True,并在循环体内直接修改 dirs 列表:dirs[:] = [d for d in dirs if d not in deleted_names],这样 walk 就不会进入已删除目录。
  • 换成 pathlib 的 rglob 收集路径再删,本质上和第一种一样——先收集、后操作。这也是为什么我推荐逻辑复杂时直接用 pathlib,少踩 os.walk 修改目录的动态坑。

4.5 filename 中含有特殊字符(空格、&、括号),shell 拼接命令会出错?

很多人处理文件名时习惯调用 os.system("rm -rf " + filepath) 或者 subprocess 直接拼字符串。一旦文件名里有空格、&、引号,命令解析就全乱了,轻则删错文件,重则执行了额外的命令。这不是 Python 库的问题,而是“用 shell 字符串拼接处理路径”这种方式的固有问题。

正确做法:文件操作永远用 Python 库函数本身,不要绕道 subprocess。如果确实需要调用外部命令,用 subprocess 的列表参数形式,避免 shell 介入:

import subprocess subprocess.run(["ls", "-l", str(path)]) # 不用 shell=True

4.6 读取目录数量巨大时内存被打爆?

os.listdir 一次性返回所有条目,如果目录里有几十万个文件,内存直接受不了。此时改用 os.scandir 或者 pathlib 的 iterdir / rglob(都是惰性迭代器)就保险得多。os.scandir 还有一个隐藏优势:它返回的 DirEntry 对象一次性携带了路径、类型、mtime 等信息,比逐个 os.path.getmtime 再 stat 一次效率高很多。处理海量文件时,这些微小的性能差异会被放大得非常明显。

另外一个容易被忽视的性能点是:同一路径上多次调用 .exists()、.stat() 其实会触发多次系统调用。批量处理时,能一次取到的信息就一次取到,别反复查。os.scandir 提供 DirEntry 就是这么用的。

5. 后续扩展与我的使用体会

写文件和目录操作的脚本,真不是背 API 大会就能写好的,它更像是一种“组织能力”——路径怎么表达、操作怎么做保护、异常怎么兜底、参数怎么暴露。今天提到的所有技巧,核心其实就四个字:【先想后写】。先确定业务边界(绝对路径还是相对路径,递归还是不递归),再确认操作风险(是否删除、是否覆盖),最后才动手写代码。这个习惯能帮你避免绝大多数文件系统相关的生产事故。

还有一个细节是我个人很喜欢的顺手技巧:给脚本第一行就加上from pathlib import Path,然后所有路径开始统一用 Path 包一层。一旦你习惯这种写法,再回去看 os.path.join 的嵌套调用,会有一种“手里的锄头变成了挖掘机”的感受。强烈建议大家把现存的小脚本尝试用 pathlib 重写一遍,不用多,两三个脚本下来,你就能体会到差异,回头自己都会忍不住想把旧代码翻新一遍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询