
1. 项目概述为什么说 glob 是 Python 文件管理的“瑞士军刀”如果你经常用 Python 处理文件比如批量重命名、筛选特定格式的图片、或者遍历某个目录下的所有配置文件那你大概率写过或者见过这样的代码用os.listdir()列出所有文件然后写一个循环里面用str.endswith(‘.txt’)或者复杂的正则表达式去匹配文件名。这样做不是不行但代码会显得冗长而且容易出错。今天要聊的glob库就是专门用来解决这个痛点的。它不是什么新潮的库而是 Python 标准库中的一员意味着你无需任何安装直接import glob就能用。它的核心价值在于让你能用一种非常直观、类似于在命令行中使用的“通配符”模式来查找匹配特定规则的文件和目录路径。想象一下这样的场景你的项目里有一个data文件夹里面塞满了各种日志文件命名规则是server_log_20240101.txt,server_log_20240102.txt… 你现在只需要找出所有一月份的文件。用glob一行代码glob.glob(‘data/server_log_202401*.txt’)就能搞定。这种简洁和高效正是它在数据处理、自动化脚本、项目构建等场景中经久不衰的原因。它特别适合那些需要根据文件名模式进行批量操作的开发者无论是刚入门的新手还是需要编写健壮工具的老鸟glob都能显著提升你处理文件的效率和代码的可读性。2. glob 的核心机制与模式匹配全解析2.1 通配符不仅仅是星号那么简单glob的核心在于其模式匹配语言它主要借鉴了 Unix shell 的规则理解这几个通配符是玩转glob的关键*匹配任意数量的任意字符包括零个字符。这是最常用的通配符。例如*.py匹配所有以.py结尾的文件。?匹配任意单个字符。比如data_?.csv可以匹配data_1.csv但不会匹配data_10.csv。[]匹配括号中列举的任意一个字符。支持两种形式枚举[abc]匹配a,b, 或c。范围[0-9]匹配任意数字[a-z]匹配任意小写字母。例如log_[0-9][0-9].txt可以匹配log_01.txt到log_99.txt。[!]或[^]匹配不在括号中列举的任意一个字符。例如[!0-9].txt匹配第一个字符不是数字的.txt文件。这里有一个非常重要的注意事项glob的匹配是基于字符串的并且是非递归的。默认情况下*不会去匹配子目录里的文件它只匹配当前层级的条目。这也是glob和一些人直觉上可能认为的“递归查找所有文件”行为不同的地方。2.2 路径分隔符与跨平台兼容性实战在模式字符串中正斜杠 (/) 被用作目录分隔符。glob库的一个聪明之处在于它的跨平台处理即使在 Windows 系统上其原生路径分隔符是反斜杠\你在模式中依然可以使用/glob会在内部进行正确处理。这让你写的代码具有更好的可移植性。但是这里有一个深坑需要警惕反斜杠转义问题。在 Python 字符串中反斜杠\是转义字符。如果你写了一个包含反斜杠的原始字符串比如 Windows 路径它可能会被 Python 和glob双重解析导致意外失败。# 错误示例在Windows上可能出错 pattern ‘C:\Users\Project\*.py‘ # ‘\U‘, ‘\P‘ 可能被解释为转义序列 # 正确做法1使用原始字符串推荐 pattern r‘C:\Users\Project\*.py‘ # 正确做法2使用正斜杠glob会自动处理 pattern ‘C:/Users/Project/*.py‘ # 正确做法3使用 os.path.join 构建路径 import os pattern os.path.join(‘C:‘, ‘Users‘, ‘Project‘, ‘*.py‘)我个人强烈推荐第二种方法使用正斜杠它最简洁且跨平台。如果路径是硬编码的这是首选。如果需要动态拼接路径则结合使用os.path.join。2.3 glob 与正则表达式的本质区别很多初学者会混淆glob模式和正则表达式。它们虽然都用于匹配但属于不同层级的工具glob更高级专为文件路径匹配设计。语法简单直观学习成本低目的明确。*.txt就是匹配所有文本文件。正则表达式更底层用于复杂的文本模式匹配。功能强大但语法复杂。要匹配所有文本文件需要写.*\.txt$。简单来说用glob来找文件用正则来“读”文件里的内容。当你需要对找到的文件内容进行复杂提取或验证时可以结合使用先用glob定位文件再用re模块处理文件内容。3. 核心函数详解与场景化应用glob模块主要提供了两个函数glob()和iglob()。还有一个escape()函数用于处理特殊字符。3.1 glob.glob()返回列表的经典用法glob.glob(pathname, *, recursiveFalse, root_dirNone, dir_fdNone)是最常用的函数。它返回一个匹配路径的列表。pathname匹配的模式字符串。recursive默认为False。当设置为True时配合**使用可以进行递归匹配。root_dir和dir_fd用于指定搜索的根目录或文件描述符在某些高级场景下使用。基础场景示例import glob # 1. 匹配当前目录所有Python文件 py_files glob.glob(‘*.py‘) print(py_files) # 输出如: [‘script1.py‘, ‘utils.py‘, ‘main.py‘] # 2. 匹配特定子目录下的JPEG图片 images glob.glob(‘static/images/*.jpg‘) print(images) # 输出如: [‘static/images/cover.jpg‘, ‘static/images/avatar.jpg‘] # 3. 使用字符范围匹配有规律命名的数据文件 # 假设有 data_Q1.csv, data_Q2.csv, data_Q3.csv, data_Q4.csv quarter_files glob.glob(‘data_Q[1-4].csv‘) print(quarter_files) # 输出: [‘data_Q1.csv‘, ‘data_Q2.csv‘, ‘data_Q3.csv‘, ‘data_Q4.csv‘]递归搜索的威力这是glob一个非常强大的特性。使用**并设置recursiveTrue可以匹配任意深度的子目录。# 匹配项目目录下所有子目录中的 .md 文件 all_md_files glob.glob(‘**/*.md‘, recursiveTrue) # 可能输出: [‘README.md‘, ‘docs/intro.md‘, ‘docs/api/overview.md‘, ‘src/notes.md‘] # 匹配任意目录下名为 config.ini 或 config.yaml 的配置文件 config_files glob.glob(‘**/config.{ini,yaml,yml}‘, recursiveTrue) # 注意{ini,yaml,yml} 是 shell 的扩展语法但 glob 本身不支持这种花括号扩展。 # 上述写法不会达到预期效果需要分别匹配或使用其他方法。注意glob模块本身不支持像{ini,yaml}这样的花括号扩展。这是 shell如 bash的功能。在 Python 中你需要分别执行多个glob或者使用fnmatch进行过滤。3.2 glob.iglob()处理海量文件的内存救星glob.iglob()的参数与glob()完全一样但关键区别在于它的返回值是一个生成器。这意味着它不会一次性将所有匹配的路径加载到内存中而是按需迭代产生每一条路径。为什么这很重要假设你要遍历一个包含数十万甚至上百万个文件的目录例如大型日志存储系统。使用glob.glob()会先花时间搜索所有文件并把这些路径全部存入一个巨大的列表消耗大量内存。而iglob()则是“找一个处理一个丢掉一个”内存占用几乎恒定。import glob # 使用 glob() - 可能内存爆炸 all_logs glob.glob(‘/var/log/**/*.log‘, recursiveTrue) # 如果文件极多列表会非常大 for log in all_logs: process(log) # 使用 iglob() - 内存友好 for log_path in glob.iglob(‘/var/log/**/*.log‘, recursiveTrue): process(log_path) # 每次循环只处理一个文件路径选择建议除非你明确知道匹配的文件数量很少比如几十个或者后续操作需要随机访问这个路径列表比如打乱顺序否则优先使用iglob()。这是一种更安全、更高效的编程习惯。3.3 glob.escape()当文件名包含特殊字符时如果你的文件名中包含了*,?,[这些glob的通配符直接用它们去匹配会出问题因为这些字符会被解释为模式语法。escape()函数就是用来转义这些特殊字符的。import glob # 假设有一个文件名为 ‘report[final].docx‘ filename ‘report[final].docx‘ # 错误方式[ 和 ] 会被解释为字符集 # glob.glob(‘report[final].docx‘) 可能会匹配到 ‘reportf.docx‘, ‘reporti.docx‘ 等 # 正确方式使用 escape 转义 safe_pattern glob.escape(‘report[final].docx‘) print(safe_pattern) # 输出: ‘report[[]final[]].docx‘ # 现在这个模式只会字面匹配 ‘report[final].docx‘ 这个文件了 matched_files glob.glob(safe_pattern)这个函数在动态构建搜索模式、且模式部分来自不可信的用户输入时比如一个文件搜索功能尤其有用可以防止模式注入错误。4. 进阶实战构建健壮的文件管理工具掌握了基础我们来看看如何将glob融入更实际的、健壮的脚本中。4.1 案例批量重命名与整理照片假设你从相机导出的照片命名是IMG_20230415_123456.jpg你想把它们批量重命名为更易读的2023-04-15_123456.jpg格式。import glob import os import re from pathlib import Path # 使用 pathlib 进行路径操作更现代 def batch_rename_photos(directory): 批量重命名指定目录下的 JPEG 照片文件。 从 IMG_YYYYMMDD_HHMMSS.jpg 重命名为 YYYY-MM-DD_HHMMSS.jpg # 使用递归查找所有 .jpg 和 .jpeg 文件 photo_patterns [‘*.jpg‘, ‘*.jpeg‘, ‘*.JPG‘, ‘*.JPEG‘] photo_files [] for pattern in photo_patterns: # 使用 Path.rglob 是另一种递归查找方式比 glob 更面向对象 photo_files.extend(Path(directory).rglob(pattern)) for photo_path in photo_files: old_name photo_path.name # 使用正则表达式提取日期和时间部分 match re.match(r‘IMG_(\d{4})(\d{2})(\d{2})_(\d{6})\.(jpe?g|JPE?G)$‘, old_name, re.IGNORECASE) if match: year, month, day, time, ext match.groups() new_name f‘{year}-{month}-{day}_{time}.{ext.lower()}‘ new_path photo_path.parent / new_name try: photo_path.rename(new_path) print(f‘Renamed: {old_name} - {new_name}‘) except OSError as e: print(f‘Error renaming {old_name}: {e}‘) else: print(f‘Skipped (pattern not match): {old_name}‘) if __name__ ‘__main__‘: # 使用当前目录下的 ‘photos‘ 文件夹 batch_rename_photos(‘./photos‘)实操心得大小写处理相机生成的文件扩展名可能是大写使用re.IGNORECASE标志或str.lower()确保匹配。路径安全使用pathlib.Path进行路径拼接和重命名操作比手动拼接字符串更安全、更直观。异常处理重命名操作可能因为权限不足、文件被占用等原因失败必须用try...except包裹。先测试后执行可以在重命名循环里先print新旧名字确认逻辑无误后再实际执行rename。4.2 案例自动化清理临时与日志文件定期清理老旧或过大的日志文件、临时文件是系统维护的常见任务。import glob import os import time from datetime import datetime, timedelta def cleanup_old_files(directory, pattern, days_old, max_size_mbNone): 清理指定目录下匹配模式、且超过一定天数或大小的文件。 Args: directory: 要清理的目录 pattern: glob 模式如 ‘*.log‘, ‘tmp_*‘ days_old: 保留最近多少天的文件 max_size_mb: 可选如果文件超过此大小MB即使未过期也删除 cutoff_time time.time() - (days_old * 86400) total_freed 0 deleted_count 0 for file_path in glob.iglob(os.path.join(directory, ‘**‘, pattern), recursiveTrue): if not os.path.isfile(file_path): continue # 跳过目录 file_stat os.stat(file_path) should_delete False reason ‘‘ # 规则1按时间清理 if file_stat.st_mtime cutoff_time: should_delete True reason f‘older than {days_old} days‘ # 规则2按大小清理如果指定了 elif max_size_mb is not None and file_stat.st_size max_size_mb * 1024 * 1024: should_delete True reason f‘larger than {max_size_mb} MB‘ if should_delete: try: file_size file_stat.st_size os.remove(file_path) total_freed file_size deleted_count 1 print(f‘Deleted: {file_path} ({reason}, size: {file_size / 1024:.1f} KB)‘) except OSError as e: print(f‘Failed to delete {file_path}: {e}‘) print(f‘\nCleanup completed. Deleted {deleted_count} files, freed {total_freed / (1024**2):.2f} MB.‘) # 示例清理 /var/log 下超过30天的 .log 文件以及任何超过100MB的日志文件 cleanup_old_files(‘/var/log‘, ‘*.log‘, days_old30, max_size_mb100)注意事项权限清理系统目录如/var/log通常需要管理员权限。确认模式在生产环境运行前可以先注释掉os.remove行只打印出将要删除的文件列表进行确认。软链接os.remove()会删除符号链接本身而不是目标文件。如果需要跟踪链接可以使用os.path.islink()判断。日志记录将删除操作记录到另一个日志文件而不是仅仅打印便于审计。4.3 与 os.walk() 的对比与选型glob的递归模式 (**) 和 Python 另一个常用工具os.walk()功能有重叠如何选择glob.glob(‘**/*.py‘, recursiveTrue):优点语法极其简洁一行代码搞定。返回的是匹配模式的完整路径列表或生成器。缺点对遍历过程的控制力较弱。你只能通过模式来筛选无法在遍历每个目录时执行自定义逻辑比如跳过某些特定命名的目录。适用场景当你只需要根据文件名模式快速拿到一个文件路径列表时。os.walk(topdir):优点控制力强。它生成一个三元组(dirpath, dirnames, filenames)。你可以在循环中直接修改dirnames列表来实现“剪枝”例如遇到.git目录时在dirnames中将其移除os.walk就不会再进入这个子目录。缺点代码稍显冗长需要自己写循环和路径拼接来过滤文件。适用场景需要复杂遍历逻辑时比如忽略隐藏目录、在进入每个目录前后执行特定操作。简单决策树任务只是“找到所有匹配*.py的文件” - 用glob。任务需要“遍历所有文件但忽略.git和__pycache__目录” - 用os.walk。在现代 Python (3.4) 中也可以考虑使用pathlib.Path.rglob()它结合了面向对象的优雅和递归查找的便利。5. 性能调优、常见陷阱与排查指南5.1 性能瓶颈分析与优化策略glob的性能主要消耗在磁盘 I/O 上尤其是在递归搜索大量文件时。减少搜索范围这是最有效的优化。尽可能使用更精确的模式并指定起始目录。# 慢从根目录开始递归找所有 .py 文件 files glob.glob(‘/**/*.py‘, recursiveTrue) # 快将搜索限定在项目源码目录 files glob.glob(‘./src/**/*.py‘, recursiveTrue)使用iglob避免内存压力如前所述对于大规模文件搜索始终使用iglob。缓存结果如果同一个目录需要被多次用相同模式搜索考虑将结果缓存起来。from functools import lru_cache lru_cache(maxsizeNone) def get_py_files(directory): return list(glob.glob(os.path.join(directory, ‘*.py‘))) # 第一次调用会扫描磁盘 files1 get_py_files(‘./src‘) # 第二次调用直接返回缓存结果 files2 get_py_files(‘./src‘)并行化搜索高级对于超大型文件系统可以考虑将顶层目录拆分使用多进程如concurrent.futures.ProcessPoolExecutor并行执行多个glob搜索最后合并结果。但这会显著增加代码复杂度需权衡利弊。5.2 十大常见问题与解决方案速查表问题现象可能原因解决方案返回空列表[]1. 模式写错无匹配项。2. 当前工作目录不对。3. 路径中的特殊字符未转义。1. 先用os.listdir()确认目录内容。2. 使用os.getcwd()和os.chdir()或使用绝对路径。3. 对包含*,?,[的文件名使用glob.escape()。递归搜索 (**) 无效recursive参数未设置为True。确保调用为glob.glob(‘**/*.py‘, recursiveTrue)。在Windows上路径匹配失败在字符串中使用了未转义的反斜杠\。使用原始字符串r‘path\*.txt‘或正斜杠‘path/*.txt‘。匹配到了隐藏文件以点开头glob的默认行为在类Unix系统上不匹配以点开头的文件。如果需要匹配隐藏文件模式必须显式包含点如.*或.[!.]*。想排除某些文件glob模式本身不支持“非”逻辑。先获取所有文件再用列表推导式过滤[f for f in glob.glob(‘*‘) if not f.endswith(‘.tmp‘)]。内存占用过高使用glob.glob()匹配了巨量文件。换用glob.iglob()进行迭代。权限错误 (PermissionError)程序对某些目录没有读取权限。用try...except包裹glob调用或提前检查目录权限。想匹配多种扩展名误以为*.{py,js}可用。glob不支持花括号扩展。需多次调用或结合fnmatchpatterns [‘*.py‘, ‘*.js‘]files [f for p in patterns for f in glob.glob(p)]符号链接处理glob默认会跟随符号链接吗在类Unix系统glob的行为通常取决于操作系统readdir的语义可能返回链接本身。如需解析可用os.path.realpath()。顺序不确定返回的文件列表顺序是任意的。如果需要特定顺序如按名称、时间对结果列表进行排序sorted(glob.glob(‘*.txt‘))5.3 调试技巧让你的 glob 模式一目了然当模式复杂或匹配结果不符合预期时可以按以下步骤调试打印当前目录首先确认你的搜索起点。import os print(‘Current working directory:‘, os.getcwd()) print(‘Directory contents:‘, os.listdir(‘.‘))分解复杂模式将一个复杂模式拆分成几个简单的部分逐步测试。# 假设你想匹配 ‘data_2024_*.csv‘ pattern ‘data_2024_*.csv‘ # 先测试基础部分 print(‘All data files:‘, glob.glob(‘data_*.csv‘)) # 再测试带年份的 print(‘2024 data files:‘, glob.glob(‘data_2024_*.csv‘))使用fnmatch.fnmatch()进行单元测试glob底层使用的是fnmatch模块。你可以直接用它来测试一个文件名是否匹配某个模式而无需实际访问文件系统。import fnmatch print(fnmatch.fnmatch(‘report_v1_final.docx‘, ‘report_v?*.docx‘)) # 输出: True print(fnmatch.fnmatch(‘data_Q1.csv‘, ‘data_Q[1-4].csv‘)) # 输出: True这在编写单元测试或逻辑验证时非常有用。glob库的简洁性背后是对文件系统操作常见需求的深刻抽象。它可能不会出现在炫酷的机器学习或网络爬虫教程的标题里但却是无数可靠脚本和工具中默默无闻的基石。掌握它意味着你处理文件时能少写许多繁琐的循环和判断让代码更加清晰和 Pythonic。下次当你需要寻找文件时别再手动listdir加过滤了试试glob你会发现它简单的外表下藏着让人惊喜的效率。