Python glob 模块完全指南:Unix 风格路径模式匹配的函数详解、实现原理与实战用例
Python glob 模块完全指南Unix 风格路径模式匹配的函数详解、实现原理与实战用例【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonglob是 Python 标准库中负责按 Unix shell 通配符模式查找文件路径的核心模块源码位于 Lib/glob.py。在 CPython 项目中凡是需要批量扫描源文件、匹配测试数据、查找匹配某种命名的资源时glob都是比手写目录遍历更简洁的方案。读完本文你将掌握glob()、iglob()、escape()、translate()四个公开 API 的完整参数语义与版本演进理解其基于os.scandir与fnmatch的分段匹配实现原理并能结合root_dir、dir_fd、recursive、include_hidden等参数写出健壮的文件查找代码。模块定位不做子 Shell 调用的准 Shell路径匹配glob模块用与 Unix shell 类似的模式匹配规则查找路径名。根据 Doc/library/glob.rst 的官方描述它不做波浪号~展开但能正确处理*、?以及用[]表达字符范围它依靠 os.scandir 与 fnmatch.fnmatch 配合完成匹配而不是真正启动一个子 shell源码 Lib/glob.py 顶部 docstring 也印证了这一点。这意味着结果中每个匹配项的类型与传入的pathname一致传入str返回str传入bytes返回bytes见 Lib/test/test_glob.py 中test_glob_literal对两种类型的断言需要~展开时请改用os.path.expanduser需要环境变量展开时请改用os.path.expandvars需要更高层的路径对象 API 时可转向pathlib其Path.glob内部正是复用了本模块的翻译与匹配引擎。两个容易混淆的隐藏文件语义glob的默认规则与fnmatch.fnmatch、pathlib.Path.glob存在一处关键差异官方文档特别标注默认情况下以点.开头的文件只能被同样以点开头的模式匹配。文档给出的对照实验是目录中同时存在card.gif与.card.gif时 import glob glob.glob(*.gif) # 不会匹配 .card.gif [card.gif] glob.glob(.c*) # 显式以点开头才能命中隐藏文件 [.card.gif]这在源码中体现为_ishidden()辅助函数Lib/glob.py——它检查路径段首字符是否为.以及_glob1在未开启include_hidden且模式本身不以点开头时对所有目录项做的过滤def _glob1(dirname, pattern, dir_fd, dironly, include_hiddenFalse): names _listdir(dirname, dir_fd, dironly) if not (include_hidden or _ishidden(pattern)): names (x for x in names if not _ishidden(x)) return fnmatch.filter(names, pattern)也就是说模式串是.*、.c*这类显式点开头时过滤逻辑自动放行隐藏项——这就是glob.glob(.c*)能匹配到.card.gif的原因。四大公开 API 与参数全景模块通过__all__暴露四个函数Lib/glob.pyglob、iglob、escape、translate。前三个自 Python 诞生早期就存在translate是 3.13 新增见 Doc/whatsnew/3.13.rst。1. glob(pathname, *, root_dirNone, dir_fdNone, recursiveFalse, include_hiddenFalse)返回匹配pathname的路径名列表可能为空。pathname必须是含路径规格的字符串可以是绝对路径如/usr/src/Python-1.5/Makefile或相对路径如../../Tools/*/*.gif并可包含 shell 风格通配符。关键语义全部来自官方文档并与源码相互印证参数默认值语义pathname必填待匹配的路径模式支持str与bytes两种形式root_dirNonepath-like 对象指定搜索的根目录效果等价于调用前先 chdir 过去但并不真正切换当前目录。pathname为相对路径时返回结果也将以root_dir为基准的相对路径呈现dir_fdNone目录文件描述符支持相对目录描述符:ref:dir_fd的路径语义recursiveFalse为真时**模式匹配任意文件与零个或多个目录、子目录及指向目录的符号链接include_hiddenFalse为真时通配符可以匹配以点.开头的路径段其余行为要点返回顺序不确定文档明确pathnames 以任意顺序返回若需特定顺序请自行 sort是否排序取决于文件系统断裂的符号链接也会被包含与 shell 行为一致源码_glob0/_glob1用_lexists判断存在性即链接本身存在即可见 Lib/glob.py调用期间文件被增删时结果不确定未定义行为若pathname含多个**且recursiveTrue可能返回重复路径名扫描文件系统抛出的所有OSError都会被抑制包括无读权限目录引发的PermissionError在大型目录树上使用**可能消耗不成比例的大量时间需要谨慎评估规模。2. iglob(...)同参数、流式产出iglob与glob参数完全相同但返回一个惰性迭代器逐个产出与glob相同的结果而不一次性存下全部路径。源码中glob其实就是iglob的list()包装def glob(pathname, *, root_dirNone, dir_fdNone, recursiveFalse, include_hiddenFalse): return list(iglob(pathname, root_dirroot_dir, dir_fddir_fd, recursiverecursive, include_hiddeninclude_hidden))在Lib/test/test_glob.py中几乎每个glob断言都会同时用assertCountEqual(glob.iglob(p, **kwargs), res)验证两个 API 结果等价Lib/test/test_glob.py因此你可以放心在需要逐条处理海量结果时选用iglob以降低内存占用。注意它同样保留glob的**多段重复路径OSError抑制等全部语义。3. escape(pathname)把任意字面串变成安全模式若你想匹配一个可能含*、?、[的字面文件名用escape把它们转义掉即可。官方说明转义对象是?、*与[三个字符并特别指出Windows 驱动器号 / UNC 共享点中的特殊字符不会被转义例如escape(//?/c:/Quo vadis?.txt) # Windows 下返回 //?/c:/Quo vadis[?].txt其实现Lib/glob.py先调用os.path.splitdrive把盘符/UNC 前缀拆开保护起来再通过magic_check正则把命中字符包进方括号magic_check re.compile(([*?[])) ... drive, pathname os.path.splitdrive(pathname) pathname magic_check.sub(r[\1], pathname) return drive pathname[?]恰好落入字符类语义而等于字面?——这也是文档中字面匹配就把元字符包进方括号建议的源码出处。测试 Lib/test/test_glob.py 覆盖了[[_/*?*/_]] → [[][[]_/[*][?][*]/_]]这类复杂串以及 Windows 专用转义用例。4. translate(pathname, *, recursiveFalse, include_hiddenFalse, sepsNone)3.13 新增的translate把带通配符的路径规格编译为可直接用于re.prefixmatch的正则表达式re.prefixmatch定义见 Lib/re/init.py。文档给出的完整可运行示例 import glob, re regex glob.translate(**/*.txt, recursiveTrue, include_hiddenTrue) regex (?s:(?:./)?[^/]*\\.txt)\\z reobj re.compile(regex) reobj.prefixmatch(foo/bar/baz.txt) re.Match object; span(0, 15), matchfoo/bar/baz.txttranslate与fnmatch.translate的本质区别在于它对路径分隔符和路径段是有感知的path-segment-aware。具体规则默认情况下通配符不跨路径分隔符匹配*一个模式段恰好匹配一个路径段recursiveTrue时**段匹配任意数量的路径段include_hiddenTrue时通配符可匹配以点开头的路径段seps可传入一个路径分隔符序列自定义分隔符集合不传时默认取os.sep与os.altsep存在时。从实现Lib/glob.py可看到它先按分隔符把模式re.split成段为每一段分别构造子正则最后整串包上(?s:...)\zres .join(results) return fr(?s:{res})\z\z绝对结尾锚点配合re.prefixmatch的从字符串开头找前缀匹配语义正好形成模式整体是否命中某路径的判定。当include_hiddenFalse时对以*或?开头的段还会前置(?!\.)负向断言拒绝隐藏段而默认不隐藏下的单段匹配形如[^/.][^/]*即首字符不能是点或分隔符。测试 Lib/test/test_glob.py 对translate产出的正则做了逐字断言例如fn(*) r(?s:[^/.][^/]*)\z、fn(**, recursiveTrue, include_hiddenTrue, seps/) r(?s:.*)\z是理解段语义的最佳教材。重要衍生pathlib 依赖它实现匹配与 globtranslate并非孤立 API——官方文档的 seealso 明确指出pathlib.PurePath.full_match与pathlib.Path.glob都调用它。追查源码可证实pathlib通过from glob import _GlobberBase、from glob import _StringGlobber, _no_recurse_symlinks复用 glob 内部的匹配引擎Lib/pathlib/types.py、Lib/pathlib/init.pyfull_match走_PathGlobber.compile()Path.glob走globber.selector(parts)Lib/pathlib/types.py而_compile_pattern正是translate之上的 512 项 LRU 缓存编译入口Lib/glob.py。换言之glob 与 pathlib 共享同一套正则翻译内核理解translate就等于理解了Path.glob匹配语义的下半层。模式语法速查与边界行为glob支持的通配符集合以*、?、[]为核心其中[]内还支持!排除、-范围、.等字符类惯用法见文档索引条目与fnmatch的字符类语义。几个文档明示的细节[!...]/[^...]排除式字符类[a-z]范围式字符类字面匹配元字符本身包进方括号如[?]匹配字面?以路径分隔符结尾的模式只匹配目录例如glob(./**/, recursiveTrue)的输出[./, ./sub/]全部以/收尾。源码中这是_glob0的分支Lib/glob.py测试 Lib/test/test_glob.py 也专门验证了aaa/命中而文件ZZZ/不命中。实战示例文档的经典文件树假设目录结构为当前目录/ ├── 1.gif ├── 2.txt ├── card.gif └── sub/ └── 3.txt官方示例完整如下注意输出保持了路径的前导部分 import glob glob.glob(./[0-9].*) [./1.gif, ./2.txt] glob.glob(*.gif) [1.gif, card.gif] glob.glob(?.gif) [1.gif] glob.glob(**/*.txt, recursiveTrue) [2.txt, sub/3.txt] glob.glob(./**/, recursiveTrue) [./, ./sub/]要点解读[0-9].*利用范围字符类先锁数字前缀再匹配任意单字符扩展名?.gif的单字符?排除了card.gif开启recursive后**/*.txt同时命中根层与sub/一层**匹配零个或更多目录因此2.txt也被包含./**/因尾分隔符而只产出目录条目。实现原理从模式到结果的四层分解glob的高层实现是一个递归分段算法。iglob先发审计事件再把真正的递归工作交给_iglobLib/glob.py。_iglob的处理流程可以概括为切分os.path.split(pathname)得到dirname与basename无通配符短路用has_magic内部就是magic_check.search判定后直接做存在性检查有 basename 用_lexists空 basename 即以/结尾则用_isdir只验证目录有通配符时按目录深度递归若dirname本身含 magic先递归_iglob(dirname, ..., dironlyTrue)收集候选目录否则把dirname作为唯一候选在候选目录内按 basename 类型分发_glob2递归**、_glob1单层通配符 fnmatch.filter、_glob0字面名存在性检查三种策略之一拼接对每个命中用os.path.join(dirname, name)还原完整相对路径。真正的目录遍历发生在_iterdirLib/glob.py它用os.scandir()逐目录扫描dironly时只保留entry.is_dir()的项并对dir_fd场景使用os.open(dirname, os.O_RDONLY | os.O_DIRECTORY, dir_fddir_fd)打开子目录后再扫描整体try/except OSError包裹——这就是权限错误被静默吞掉的实现来源。root_dir 与 dir_fd 的底层行为root_dir传入后先经os.fspath()归一化为路径Lib/glob.py随后所有_join(root_dir, ...)都挂在该根下语义上等价临时 chdir但测试用change_cwd对照验证了两者一致性Lib/test/test_glob.pydir_fd依赖平台对目录描述符的支持。测试工具里用os.open(self.tempdir, os.O_RDONLY | os.O_DIRECTORY)打开 fd 并在{os.open, os.stat} os.supports_dir_fd时才启用Lib/test/test_glob.py说明跨平台使用时需要判断os.supports_dir_fd递归遍历**会跟随指向目录的符号链接测试 Lib/test/test_glob.py 中sym3 - a/bcd被**命中即为佐证这也是文档警告大目录树耗时的原因之一——链接可能形成环/重复访问pathlib侧则通过_no_recurse_symlinks哨兵对象在recurse_symlinksFalse时禁止跟随。bytes 路径整条链路对bytes同样生效magic_check_bytes处理字节模式Lib/glob.py_isrecursive同时比较b**与**。测试几乎为每个场景都补做了os.fsencode后的 bytes 断言Lib/test/test_glob.py。审计事件可观测的文件访问glob是文件系统访问类 API 中启用了审计钩子的标准库函数。iglob每次调用会触发两个事件Lib/glob.pysys.audit(glob.glob, pathname, recursive) sys.audit(glob.glob/2, pathname, recursive, root_dir, dir_fd)即glob.glob携带pathname, recursive与glob.glob/2额外携带root_dir, dir_fd便于审计方获知真实的搜索根。glob()、iglob()共用同一审计点。若你的应用运行在启用sys.addaudithook的强化环境如企业安全代理、-P隔离模式可通过这两个事件记录所有 glob 查询。版本演进时间线以官方文档的 versionchanged / versionadded 注释为准结合当前仓库Include/patchlevel.h 显示为 3.16.0a0 开发版整理如下版本变更3.4新增escape()3.5为glob/iglob新增recursive参数支持**递归匹配3.10新增root_dir与dir_fd参数3.11新增include_hidden参数3.13新增translate()见 Doc/whatsnew/3.13.rst3.15移除长期未公开的glob0、glob1辅助函数官方建议改用glob加root_dir见 3.15 Whats New写作时请注意root_dir/dir_fd/include_hidden均为关键字专属参数签名中*之后的全部参数调用时只能以关键字方式传入你的运行环境需按上表对照特性是否可用。与 fnmatch、pathlib 的协同与取舍fnmatchDoc/library/fnmatch.rst只做文件名不含路径分隔符语义的通配匹配不扫描文件系统glob负责找出所有符合模式的路径fnmatch负责判定单个名字是否匹配。glob单层目录匹配内部就是用fnmatch.filter完成的pathlib提供面向对象的高层路径 API其Path.glob/full_match复用 glob 的translate与_GlobberBase引擎语义与glob保持同源需要返回Path对象、链式路径操作或对大小写敏感度有精细控制时选 pathlib需要纯str/bytes、dir_fd、root_dir这些底层能力时选 glob 模块取舍原则可概括为单文件判定用 fnmatch路径批量匹配要文件系统结果用 glob要路径对象/纯正则匹配用 pathlib translate。测试与验证资源CPython 为 glob 模块配备了覆盖面很广的回归测试 Lib/test/test_glob.py487 行是学习边界行为的活教材重点用例包括test_glob_empty_pattern空模式、bytes 空模式、配合root_dir/dir_fd均返回[]test_glob_directory_with_trailing_slash尾分隔符模式对目录/文件的差异化命中test_glob_symlinks与test_glob_broken_symlinks正常与断裂符号链接的包含语义test_recursive_glob**的各种组合**、a/**、**/EF、**/*F等以及include_hiddenTrue下隐藏文件的纳入test_translate*系列对translate输出正则的逐字断言覆盖recursive、include_hidden、自定义seps的矩阵test_glob_many_open_files30 层深目录 × 100 个并行iglob迭代器验证无文件描述符泄漏对应_iterdir中用完即关scandir与_StringGlobber.scandir提前关闭迭代器的实现见 Lib/glob.py。要实际复现官方示例只需在任意空目录中创建1.gif、2.txt、card.gif与含3.txt的sub/再用交互式 Python 依次执行上文示例代码即可运行python -m test test_glob需在已构建的 CPython 源码树内可验证本机行为与官方测试一致。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考