前言文件操作大家讲得多目录操作反而常被忽略结果一到真实项目就卡壳日志目录不存在、输出目录已存在、临时文件忘了删、递归遍历把隐藏目录也扫了进来。这些场景都属于「目录」这一层的操作。一个典型误解是「建目录就用os.mkdir」。os.mkdir只能建一层父目录不存在照样报错要逐级创建得用os.makedirs。类似地「删目录」也分三种os.rmdir只删空目录os.removedirs自底向上删空链shutil.rmtree才删整棵树。本文围绕目录展开创建与删除、用pathlib做遍历和筛选、临时目录的正确用法最后给一个「安全清理过期文件」的实战脚本。示例面向 Python 3.8用到更高版本特性时会注明。一、创建、删除与重命名目录os这一侧的接口# 适用于 Python 3.8import osos.mkdir(out) # 建一层父目录不在就抛 FileNotFoundErroros.makedirs(logs/2026/10, exist_okTrue) # 逐级建exist_okTrue 已存在不报错os.rmdir(out) # 只能删空目录os.removedirs(a/b/c) # 自底向上删空目录遇到非空就停os.makedirs的签名是os.makedirs(name, mode0o777, exist_okFalse)。注意exist_ok只对「目标目录已存在」放行如果目标存在但是文件照样抛错。mode决定权限位但会被进程的 umask 修正所以实际权限可能比0o777更严。pathlib一侧# 适用于 Python 3.8from pathlib import Pathp Path(logs) / 2026 / 10p.mkdir(parentsTrue, exist_okTrue)q Path(out)q.rmdir() # 同样只能删空目录Path.mkdir(parentsTrue, exist_okTrue)这一个方法就覆盖了os.mkdir和os.makedirs两种需求这是pathlib比较顺手的地方。重命名目录用os.rename/os.replacepathlib里是Path.rename()/Path.replace()操作os 写法pathlib 写法建一级目录os.mkdir(p)Path(p).mkdir()建多级目录os.makedirs(p, exist_okTrue)Path(p).mkdir(parentsTrue, exist_okTrue)删空目录os.rmdir(p)Path(p).rmdir()删整树shutil.rmtree(p)shutil.rmtree(p)改名os.rename(src, dst)Path(src).rename(dst)覆盖式改名os.replace(src, dst)Path(src).replace(dst)二、用 pathlib 遍历与筛选目录Path.iterdir()相当于os.listdir()但返回的是Path对象而不是字符串省掉反复拼接# 适用于 Python 3.8from pathlib import Pathfor entry in Path(.).iterdir():kind 目录 if entry.is_dir() else 文件print(kind, entry.name)要做模式匹配用Path.glob()当前层和Path.rglob()递归# 适用于 Python 3.8from pathlib import Path# 当前目录下所有 .csvfor p in Path(data).glob(*.csv):print(p)# 递归找所有 .py等价于 glob(**/*.py)for p in Path(.).rglob(*.py):print(p)glob和rglob返回的都是生成器不会一次性把所有匹配项算出来。若路径不存在glob只是产出空结果不报错——这既是优点也容易掩盖「目录写错了」这种低级问题。筛选时另一个常见需求是「只看文件、跳过隐藏项」# 适用于 Python 3.8from pathlib import Pathfiles [p for p in Path(.).iterdir()if p.is_file() and not p.name.startswith(.)]print(len(files), 个可见文件)注意Path.iterdir()返回的条目顺序取决于文件系统需要稳定顺序一定要自己sorted()。三、临时文件与临时目录tempfile模块负责「用完就删」的临时资源。首选上下文管理器写法退出时自动清理# 适用于 Python 3.8import tempfilefrom pathlib import Pathwith tempfile.TemporaryDirectory() as tmp:work Path(tmp) / stage.txtwork.write_text(临时内容, encodingutf-8)print(工作目录, tmp)# 离开 with 后整个目录连同内容一起被删除需要临时文件并且要让别的程序也能按名字打开时用NamedTemporaryFile# 适用于 Python 3.8import tempfilewith tempfile.NamedTemporaryFile(modew, suffix.txt,encodingutf-8, deleteTrue) as fp:fp.write(hello)fp.flush() # 让内容真正落盘别人才读得到print(fp.name)几个准确的事实NamedTemporaryFile的签名是NamedTemporaryFile(modewb, buffering-1, encodingNone, newlineNone, suffixNone, prefixNone, dirNone, deleteTrue, *, errorsNone, delete_on_closeTrue)delete_on_close是Python 3.12才加入的参数在此之前只有deleteTemporaryDirectory的delete参数同样是 3.12 加入的。写兼容 3.8~3.11 的代码时不要用这两个参数。底层接口tempfile.mkdtemp(suffixNone, prefixNone, dirNone)直接建一个临时目录并返回它的路径但不会自动删除清理责任在使用者身上# 适用于 Python 3.8import shutilimport tempfiled tempfile.mkdtemp(prefixbuild_)try:print(临时目录, d)finally:shutil.rmtree(d, ignore_errorsTrue)tempfile.gettempdir()返回系统用来放临时文件的目录TemporaryDirectory()不传dir时就落在这里。实战清理超过 N 天的日志文件下面脚本演示「目录创建 递归筛选 文件信息 删除」的组合重点是删除前先确认目标是文件# 适用于 Python 3.8import timefrom pathlib import Pathdef clean_logs(rootlogs, days7, dry_runTrue):root Path(root)if not root.is_dir():raise NotADirectoryError(f{root} 不是目录)deadline time.time() - days * 86400removed 0for path in root.rglob(*.log):if not path.is_file():continueif path.stat().st_mtime deadline:print((将删除 if dry_run else 删除 ) str(path))if not dry_run:path.unlink()removed 1return removedif __name__ __main__:print(命中, clean_logs(), 个文件预演模式)脚本默认dry_runTrue先打印再决定是否真删这是处理删除逻辑的稳妥做法。path.unlink()只删文件遇到目录会抛IsADirectoryError正好当作一道保险。常见坑点1. 用os.mkdir建多级目录❌os.mkdir(a/b/c)a不存在时直接抛FileNotFoundError。 ✅os.makedirs(a/b/c, exist_okTrue)或Path(a/b/c).mkdir(parentsTrue, exist_okTrue)。2. 以为rmdir能删非空目录❌os.rmdir(build)里还有文件报OSError: Directory not empty。 ✅ 明确要整树删除时用shutil.rmtree(build)并二次确认路径。3. 重复执行脚本被FileExistsError打断❌Path(out).mkdir()第二次运行报错。 ✅ 加exist_okTrue。4. 遍历目录时顺序不确定❌ 假设iterdir()返回「按名字排好序」的列表用它做分页或对比。 ✅sorted(Path(.).iterdir())或sorted(p.name for p in ...)。5. 临时目录不用上下文管理器❌tempfile.mkdtemp()之后忘了删临时目录越堆越多。 ✅mkdtemp用try/finally配shutil.rmtree或直接用TemporaryDirectory()。6.glob匹配不到就以为代码错了❌Path(data).glob(*.csv)因为data不存在而静默返回空误判成「没有数据」。 ✅ 匹配前先Path(data).is_dir()路径不对就尽早报错。7. Windows 上删除仍被占用的文件❌shutil.rmtree(tmp)报PermissionError因为里面的文件还开着。 ✅ 先确保文件句柄已关闭必要时用TemporaryDirectory(ignore_cleanup_errorsTrue)。8. 递归遍历撞上符号链接成环❌rglob在存在循环符号链接的目录上可能反复进出。 ✅ 检查path.is_symlink()并跳过或改用不跟随链接的遍历方式。总结场景推荐写法注意建多级目录Path(p).mkdir(parentsTrue, exist_okTrue)目标为文件时仍报错删空目录os.rmdir/Path.rmdir非空会失败删整树shutil.rmtree危险先确认路径列目录Path.iterdir()顺序不保证模式匹配Path.glob/Path.rglob路径不存在时静默为空临时目录tempfile.TemporaryDirectory()自动清理临时文件tempfile.NamedTemporaryFile()需要落盘时先flush()目录操作的难点不在 API而在「已存在」「非空」「被占用」「路径写错」这几种边界。把exist_ok用起来、把删除做成可选、把路径先判断再使用脚本的健壮性会立刻上一个台阶。