资讯中心

Python 用 pathlib 取代 os.path:路径拼接、遍历与文件读写的现代写法

📅 2026/7/30 13:28:53
Python 用 pathlib 取代 os.path:路径拼接、遍历与文件读写的现代写法
Python 用 pathlib 取代 os.path:路径拼接、遍历与文件读写的现代写法你有没有写过这样的代码:importos base/data/logstoday2026-07-30pathos.path.join(base,today,app.log)ifos.path.exists(path):withopen(path)asf:contentf.read()parentos.path.dirname(path)nameos.path.basename(path)extos.path.splitext(name)[1]功能没问题,但读起来很割裂:os.path.join、os.path.dirname、os.path.splitext全是散装函数,路径是个字符串,你得记住一堆函数名。Python 3.4 之后有了pathlib,把路径变成对象,上面这堆代码能写得清爽得多。这篇就从实际场景出发,把os.path逐步换成pathlib。第一步:路径拼接从函数变成运算符os.path.join最常见,pathlib用/运算符替代:frompathlibimportPath basePath(/data/logs)pathbase/2026-07-30/app.logprint(path)# /data/logs/2026-07-30/app.logprint(type(path))# class pathlib.PosixPath/在这里被重载成了「拼路径」,读起来跟真实路径长得一样。它自动处理分隔符,你不用关心是/还是\——在 Windows 上Path会是WindowsPath,拼出来自动用反斜杠。注意/左边必须是Path对象,右边可以是字符串:# 对:左边是 PathpPath(/data)/logs/app.log# 错:纯字符串用 / 是除法,会报 TypeError# p /data / logs第二步:拆解路径不再靠一堆函数os.path.dirname / basename / splitext对应pathlib的属性,全挂在对象上:pPath(/data/logs/2026-07-30/app.log)print(p.parent)# /data/logs/2026-07-30 (等价 dirname)print(p.name)# app.log (等价 basename)print(p.stem)# app (去掉后缀的文件名)print(p.suffix)# .log (等价 splitext[1])print(p.parts)# (/, data, logs, 2026-07-30, app.log)p.parent还能连续取,拿到爷爷目录:print(p.parent.parent)# /data/logs# 或者直接用 parents 索引print(p.parents[1])# /data/logs改后缀也很方便,不用先 split 再拼:# 把 app.log 改成 app.log.gzprint(p.with_suffix(.log.gz))# /data/logs/2026-07-30/app.log.gz# 换文件名print(p.with_name(error.log))# /data/logs/2026-07-30/error.log第三步:存在性判断与文件读写os.path.exists / isfile / isdir变成方法:pPath(/data/logs/app.log)p.exists()# 是否存在p.is_file()# 是不是文件p.is_dir()# 是不是目录最爽的是pathlib自带读写方法,一行搞定小文件读写,不用再手写with open:configPath(config.txt)# 一行读全文textconfig.read_text(encodingutf-8)# 一行写(会覆盖)config.write_text(port8080\n,encodingutf-8)# 二进制同理dataPath(logo.png).read_bytes()这几个方法内部帮你开关文件句柄,适合一次性读写的小文件。大文件还是要用with p.open()流式处理:withp.open(encodingutf-8)asf:forlineinf:# 逐行,不会把整个文件读进内存process(line)p.open()和内置open(p)效果一样,参数也一致,只是写法更贴着对象。第四步:创建目录别再手写递归判断老代码经常这样保证目录存在:importos d/data/logs/2026-07-30ifnotos.path.exists(d):os.makedirs(d)pathlib用mkdir的两个参数一步到位:dPath(/data/logs/2026-07-30)d.mkdir(parentsTrue,exist_okTrue)parentsTrue:父目录不存在就一起建(等价makedirs)。exist_okTrue:目录已存在也不报错(否则会抛FileExistsError)。这里是很多人踩的坑:不加exist_okTrue,并发或重复运行时目录已存在就会炸。加上它,逻辑才幂等。第五步:遍历目录,glob 比 os.listdir 强太多要找某目录下所有.log文件,老写法得os.listdir 手动过滤后缀:importos logs[fforfinos.listdir(/data/logs)iff.endswith(.log)]# 而且这里拿到的只是文件名字符串,还得自己拼回全路径pathlib用glob直接出Path对象:logs_dirPath(/data/logs)# 当前目录下的 .logforfinlogs_dir.glob(*.log):print(f)# 已经是完整 Path,可直接 f.read_text()# 递归所有子目录的 .log,用 ** 通配符forfinlogs_dir.rglob(*.log):print(f,f.stat().st_size)# stat 拿文件大小、修改时间等rglob(*.log)等价glob(**/*.log),递归子目录。返回的是生成器,大目录不会一次性把所有路径塞进内存。一个实战例子:找出目录里所有超过 100MB 的日志文件:frompathlibimportPathdeffind_big_logs(root:str,min_mb:int100):root_pathPath(root)forfinroot_path.rglob(*.log):# st_size 是字节,转成 MB 比较size_mbf.stat().st_size/1024/1024ifsize_mbmin_mb:yieldf,round(size_mb,1)forpath,mbinfind_big_logs(/data/logs):print(f{path}:{mb}MB)比os.walk 手动拼路径 手动判断后缀清爽多了。一个坑:Path 对象传给旧库要转字符串绝大多数标准库(open、json.load、shutil等)都直接吃Path对象。但有些老第三方库只认字符串,传Path会报错。这时显式转一下:pPath(/data/config.json)# 遇到只认 str 的旧 APIsome_old_lib.load(str(p))反过来,判断两个路径是否指向同一个文件,别直接比字符串(/data/./app.log和/data/app.log字符串不等但其实同一个),用resolve()规范化后再比:aPath(/data/./logs/../logs/app.log)bPath(/data/logs/app.log)print(ab)# False,字符串不同print(a.resolve()b.resolve())# True,解析成绝对规范路径后相等resolve()会把.、..、软链接都解析掉,得到真实的绝对路径。涉及路径去重、安全校验(防目录穿越)时必须用它。小结从os.path迁到pathlib,记住这张对照表就够上手了:拼路径:os.path.join(a, b)→Path(a) / b取目录/文件名:dirname/basename→.parent/.name,去后缀名用.stem,后缀用.suffix判断存在:os.path.exists/isfile/isdir→.exists()/.is_file()/.is_dir()建目录:os.makedirs(d)→d.mkdir(parentsTrue, exist_okTrue)读写小文件:.read_text()/.write_text()一行搞定遍历:os.listdir 过滤 →.glob(*.log)/ 递归.rglob(*.log)规范化再比较:涉及安全或去重先.resolve()一句话记忆点:把路径当对象,而不是字符串——拼接用/、拆解用属性、遍历用glob,os.path那一堆散装函数基本可以退休了。