Python glob模块实战:从基础通配符到递归遍历的完整指南
1. 为什么需要glob模块?
在日常开发中,文件操作是最常见的需求之一。想象一下这样的场景:你需要批量处理某个文件夹下的所有图片文件,或者查找项目中所有以_test.py结尾的测试文件。如果手动一个个文件去检查,不仅效率低下,而且容易出错。这时候就需要glob模块来帮忙了。
我第一次接触glob是在处理一个图片分类项目时。当时需要从数千张图片中筛选出所有.jpg格式的风景照片,手动操作几乎不可能完成。glob模块用一行代码就解决了这个问题:
import glob photos = glob.glob('images/*.jpg')glob模块是Python标准库中的文件路径查找工具,它最大的特点就是支持Unix风格的路径名模式扩展。简单来说,就是可以用我们熟悉的通配符(比如*、?、[])来匹配文件和目录。这比直接使用os.listdir()后再用正则表达式过滤要方便得多。
2. glob模块基础用法
2.1 安装与导入
glob模块是Python标准库的一部分,这意味着你不需要额外安装任何包。只需要在代码开头简单导入即可:
import glob2.2 基本通配符使用
glob模块支持三种基本的通配符:
*:匹配任意数量的字符(包括零个字符)?:匹配单个字符[]:匹配指定范围内的字符
来看几个实际例子。假设我们有以下目录结构:
project/ ├── main.py ├── utils.py ├── data/ │ ├── 2023_report.csv │ ├── 2023_summary.xlsx │ └── 2024_plan.docx └── tests/ ├── test_main.py └── test_utils.py要查找所有Python文件:
py_files = glob.glob('project/*.py') # 返回: ['project/main.py', 'project/utils.py']查找data目录下所有2023年的文件:
data_2023 = glob.glob('project/data/2023*') # 返回: ['project/data/2023_report.csv', 'project/data/2023_summary.xlsx']2.3 匹配特定字符集
有时候我们需要更精确的匹配。比如只想获取测试文件:
test_files = glob.glob('project/tests/test_?.py') # 返回: ['project/tests/test_main.py', 'project/tests/test_utils.py']或者匹配特定数字开头的文件:
specific_files = glob.glob('project/data/[23]*') # 返回: ['project/data/2023_report.csv', 'project/data/2023_summary.xlsx', 'project/data/2024_plan.docx']3. 高级文件匹配技巧
3.1 递归目录遍历
当我们需要处理多层嵌套的目录结构时,递归查找就派上用场了。glob模块通过**通配符和recursive=True参数实现这一功能。
继续使用上面的目录结构,假设我们想查找项目中所有的.py文件,包括子目录中的:
all_py_files = glob.glob('project/**/*.py', recursive=True) # 返回: ['project/main.py', 'project/utils.py', 'project/tests/test_main.py', 'project/tests/test_utils.py']这里有几个关键点需要注意:
**表示匹配任意层级的子目录- 必须设置
recursive=True才能启用递归查找 - 路径模式要以
**开头或包含**才能递归
3.2 处理隐藏文件
默认情况下,glob不会匹配以点(.)开头的隐藏文件(Unix/Linux系统中的隐藏文件)。这是有意为之的设计,因为这类文件通常是系统或配置文件。
如果需要匹配隐藏文件,可以显式指定:
hidden_files = glob.glob('.*')3.3 转义特殊字符
如果文件名中包含glob的特殊字符(如*、?、[等),可以使用glob.escape()函数进行转义:
escaped_path = glob.escape('special*file.txt') safe_files = glob.glob(escaped_path)4. glob.glob与glob.iglob的深度对比
4.1 返回类型差异
glob.glob()直接返回匹配路径的列表,而glob.iglob()返回一个生成器。这在处理大量文件时有显著区别。
# glob.glob返回列表 files_list = glob.glob('large_dir/**/*.txt', recursive=True) # glob.iglob返回生成器 files_iter = glob.iglob('large_dir/**/*.txt', recursive=True)4.2 内存使用比较
对于包含数千甚至数百万文件的大型目录,glob.glob()会一次性将所有匹配路径加载到内存中,可能导致内存不足。而glob.iglob()因为是惰性求值,只在需要时才生成下一个路径,内存占用更小。
我曾经处理过一个包含50万+图片文件的项目,使用glob.glob()直接导致内存溢出,换成glob.iglob()后问题解决:
# 危险:可能内存不足 all_images = glob.glob('huge_image_lib/**/*.jpg', recursive=True) # 安全:内存友好 for image_path in glob.iglob('huge_image_lib/**/*.jpg', recursive=True): process_image(image_path)4.3 性能实测
我做了一个简单的性能测试,在一个包含10,000个文件的目录中查找所有.txt文件:
import time start = time.time() list(glob.iglob('test_dir/**/*.txt', recursive=True)) print(f"iglob耗时: {time.time()-start:.4f}秒") start = time.time() glob.glob('test_dir/**/*.txt', recursive=True) print(f"glob耗时: {time.time()-start:.4f}秒")测试结果显示,两者在查找时间上差异不大,但内存使用差异显著。因此建议:
- 文件数量少时,用
glob.glob()更简单 - 文件数量多或不确定时,用
glob.iglob()更安全
5. 实际应用案例
5.1 批量重命名文件
假设我们需要将某个目录下所有的.jpg文件改为.png格式:
import os for jpg_file in glob.iglob('photos/**/*.jpg', recursive=True): png_file = jpg_file[:-4] + '.png' os.rename(jpg_file, png_file)5.2 项目文件统计
统计Python项目中各类文件的数量:
file_types = { 'Python': len(glob.glob('**/*.py', recursive=True)), 'Markdown': len(glob.glob('**/*.md', recursive=True)), 'Images': len(glob.glob('**/*.{jpg,png,gif}', recursive=True)) }5.3 构建自动化测试套件
动态发现并运行所有测试文件:
import unittest def discover_tests(): test_files = glob.glob('tests/**/test_*.py', recursive=True) for file in test_files: module_name = file.replace('/', '.').replace('\\', '.')[:-3] __import__(module_name) unittest.main()6. 常见问题与解决方案
6.1 路径分隔符问题
在不同操作系统中,路径分隔符可能不同(Windows用\,Unix用/)。为了保证代码跨平台兼容性,建议:
# 不推荐 files = glob.glob('folder\subfolder\*.txt') # 推荐 files = glob.glob('folder/subfolder/*.txt')glob模块内部会处理路径分隔符的转换。
6.2 匹配不到预期文件
如果发现glob没有匹配到预期的文件,可以检查以下几点:
- 当前工作目录是否正确(使用os.getcwd()查看)
- 路径模式是否正确,特别是递归查找时需要
** - 是否设置了
recursive=True参数 - 文件名是否包含需要转义的特殊字符
6.3 性能优化建议
当处理超大型文件系统时,可以考虑以下优化:
- 尽量缩小匹配范围,比如指定更具体的目录层级
- 使用更精确的通配符减少匹配数量
- 考虑结合os.scandir()等更低级API
- 对于重复查找,可以缓存结果
7. 与其他模块的配合使用
7.1 结合os模块
glob返回的是字符串路径,通常需要配合os模块进行进一步操作:
import os for file in glob.iglob('**/*.tmp', recursive=True): if os.path.getsize(file) == 0: # 检查文件大小 os.remove(file) # 删除空临时文件7.2 与pathlib整合
Python 3.4+引入了pathlib模块,它提供了更面向对象的路径操作方式:
from pathlib import Path py_files = Path('.').glob('**/*.py') for py_file in py_files: print(f"Found Python file: {py_file}")pathlib的glob方法与标准库glob模块功能类似,但返回的是Path对象而非字符串。
7.3 在数据处理中的应用
结合pandas等数据分析库批量读取CSV文件:
import pandas as pd all_data = [] for csv_file in glob.iglob('data/**/*.csv', recursive=True): df = pd.read_csv(csv_file) all_data.append(df) combined = pd.concat(all_data)8. 深入理解glob实现原理
虽然日常使用不需要了解内部实现,但知道glob如何工作有助于更好地使用它。
glob模块实际上是基于fnmatch模块实现的,后者提供了文件名匹配的功能。当我们调用glob.glob()时,大致会发生以下步骤:
- 解析输入的路径模式,分离目录部分和文件名模式部分
- 如果是递归模式(包含**),则遍历所有子目录
- 在每个目录中,使用fnmatch.fnmatch()匹配文件名
- 收集所有匹配的路径并返回
了解这一点后,我们就能理解为什么glob在某些情况下可能不如专门的目录遍历工具高效。对于极端性能敏感的场景,可能需要考虑使用os.walk()或第三方库如scandir。
