# Python Glob 模块:一个被低估的文件查找利器
去年有次处理一个客户的数据迁移项目,那家伙把几万个日志文件分散在二十多个子目录里,文件名格式还混着日期、时间戳和随机字符串。当时团队有人提议用os.listdir递归遍历,有人想上正则表达式。后来我用glob模块,三行代码搞定了文件筛选。这种事干多了就会明白,Python标准库里有些小工具,平时不起眼,用对地方真能省下大把时间。
到底是什么
glob本质上就是个用通配符模式来匹配文件路径的工具。它的名字来源于Unix shell里的glob命令,那个把星号和问号转译成文件匹配规则的老家伙。Python把同样的理念搬了过来,让你能像在命令行里敲ls *.txt那样,在代码里按模式查找文件。
和os.listdir加filter的做法不同,glob直接理解模式语法。你写**/*.csv,它就知道要递归查找所有子目录下的CSV文件。不需要自己写循环、不需要处理各种路径边缘情况,它把这些琐事都包了。
能解决什么问题
最常见的场景是批量处理文件。比如说,需要处理某个目录下所有以"2024"开头的Excel文件。写一个循环遍历所有文件然后逐个判断文件名是否以"2024"开头当然可以,但glob只要glob("2024*.xlsx")就完事了。
处理嵌套目录结构时优势更明显。项目里有个存储图片的系统,按年/月/日/设备编号的层级摆放文件。想找出所有设备A001在2023年10月拍的产品照片,用glob("2023/10/*/A001_*.jpg")。这种模式,用其他方法实现起来代码量会翻好几倍。
临时调试时也特别顺手。比如代码出了bug,怀疑是某些特定文件导致的,用glob快速找出符合特征的文件集合,逐批测试排除。不用写完整脚本,Python交互式环境里敲几行就能搞定。
如何使用
glob模块主要提供三个函数:glob()、iglob()和escape()。glob()返回匹配结果的列表,文件多的时候占内存。iglob()是个生成器,遍历大目录时不会一次性把所有路径全塞到内存里。escape()比较特殊,当你要匹配的文件名里本身包含星号或问号时,用它转义一下。
一个实际例子。假设要处理项目目录下的所有Python文件,但要排除测试文件:
fromglobimportglobfrompathlibimportPath source_dir=Path("src")files=glob("src/**/*.py",recursive=True)test_files=glob("src/**/test_*.py",recursive=True)production_files=[fforfinfilesiffnotintest_files]这里**表示任意层级的目录,recursive=True必须设置,否则**只匹配当前目录。这个参数容易忘,刚用glob时吃过几次亏。
还有个容易被忽略的点:glob默认不匹配以点号开头的隐藏文件。Linux系统下那些以点开头的配置文件、临时文件,glob在匹配时会把它们跳过去。要包含这类文件,得用glob(".*")显式指定。
最佳实践
文件数量大时优先用iglob。有次处理一个包含十万多张图片的目录,glob()返回列表时直接占了近十兆内存。改用iglob后,边遍历边处理,内存占用降到最低。
路径处理建议结合pathlib用。glob返回的是字符串路径,直接操作字符串拼接、判断有点别扭。用Path对象包裹一下,.name、.suffix、.parent这些属性用起来顺手得多。
fromglobimportiglobfrompathlibimportPathforpath_striniglob("data/**/*.csv",recursive=True):path=Path(path_str)ifpath.stat().st_size>1024:# 跳过小文件process_csv(path)注意跨平台兼容性。Windows路径分隔符是反斜杠,Linux和Mac是正斜杠。好的做法是写模式时统一用正斜杠,让glob自己去适配。当然实际项目中绝大多数开发环境都是Mac或Linux,这个问题不那么常见。但如果团队里有人用Windows,或者代码需要在CI/CD平台的Windows节点上跑,就得注意这一点。
路径匹配模式写得太宽泛容易出问题。记着有个项目里用了**/*.txt匹配,结果跑了几分钟后才发现某个用户的目录下有个一百多层的目录结构,全遍历了一遍。遇到不确定的情况,可以先在交互环境里测试一下匹配结果的范围,再加递归。
和其他方法对比
os.listdir配合字符串方法是最朴素的做法。它直接、简单,没有学习成本。但要处理递归目录或复杂匹配时,代码量急剧膨胀。而且自己写递归遍历很容易遗漏某些边缘情况,比如权限不足的目录、死循环符号链接。
pathlib的glob方法本质上是相同的东西,只是接口风格更现代。Path.glob("**/*.py")和glob("**/*.py", recursive=True)功能几乎等价。选哪个看个人偏好和团队习惯。如果项目中大量用pathlib操作路径,顺着用它的glob方法更自然。如果只是偶尔查个文件,直接import glob模块更轻量。
有人会用正则表达式来匹配路径。比如re.search(r'\.(py|txt)$', filename)来过滤文件类型。这种方式灵活性最大的写法了,几乎任何文件名的模式都能表述。但正则表达式写起来费脑,维护起来费神。除非文件名规则极其复杂、通配符表达不了,否则用glob更省心。
第三方库方面,fnmatch提供了glob类似的通配符匹配能力,直接用在字符串上而不涉及实际文件系统。如果只是想判断某个字符串是否符合模式、并不真的要从磁盘上找文件,用它比glob合适。整体来说,对于百分之九十的文件查找场景,内置的glob就是最佳选择。