Python用fnmatch过滤列表中的通配符文件名
详细讲解如何在Python中使用标准库fnmatch模块对内存中的文件名列表进行通配符模式过滤,包括fnmatch.filter的使用、通配符语法、跨平台大小写处理及与glob的区别。
当我们在 Python 中处理文件名时,经常会拿到一个包含大量文件名的纯字符串列表(例如解压包目录清单、网络接口返回的文件名列表或 os.listdir() 的输出),并希望像在命令行中那样使用 *.py 或 data_??.csv 这样的通配符快速选出目标项。Python 内置的 fnmatch 模块正是完成这项任务的核心工具,其中的 fnmatch.filter() 能够直接对内存列表执行通配符过滤,省去手动编写正则或遍历磁盘的繁琐开销。
一、核心解决方案:使用 fnmatch.filter()
如果已经有一个文件名列表,最直接且高效的方法是调用 fnmatch.filter(names, pattern)。它接收一个字符串可迭代对象和一个包含通配符的模式串,返回匹配该模式的新列表。
import fnmatch
file_names = [
"app.py",
"test_app.py",
"config.yaml",
"database.sql",
"log_2026_01.txt",
"log_2026_02.txt",
"report.pdf"
]
# 过滤出所有以 .py 结尾的文件
python_files = fnmatch.filter(file_names, "*.py")
print("Python脚本列表:", python_files)
# 过滤出特定前缀的日志文件
monthly_logs = fnmatch.filter(file_names, "log_2026_??.txt")
print("2026年日志列表:", monthly_logs)
示例输出:
Python脚本列表: ['app.py', 'test_app.py']
2026年日志列表: ['log_2026_01.txt', 'log_2026_02.txt']
与使用列表推导式配合单个匹配函数相比,fnmatch.filter() 内部做了针对性优化,在处理大规模列表时速度更快、意图更清晰。
二、fnmatch 支持的通配符语法
fnmatch 遵循 Unix Shell 风格的通配符规则(Glob 语法),不同于完整的正则表达式,其语法非常精炼:
| 模式符号 | 含义解释 | 示例 | 匹配项举例 |
|---|---|---|---|
* |
匹配任意长度的字符序列(包括空字符) | *.csv |
data.csv, .csv, user_info.csv |
? |
匹配任意单个字符 | file_?.txt |
file_1.txt, file_a.txt(不匹配 file_10.txt) |
[seq] |
匹配方括号序列内的任意单个字符 | image_[0-9].png |
image_3.png, image_7.png |
[!seq] |
匹配不在方括号序列内的任意单个字符 | temp_[!a-z].dat |
temp_1.dat, temp_#.dat(不匹配 temp_c.dat) |
[!seq],而不是正则表达式中常见的脱字符 [^seq]。虽然部分扩展环境也能容忍 ^,但为了保证跨环境兼容性,务必遵循标准写为 [!...]。
三、函数矩阵:选择适合业务场景的接口
fnmatch 模块提供了四个主要函数。理解它们的定位有助于避免代码冗余和隐蔽的跨平台缺陷:
import fnmatch
filename = "MainController.PY"
# 1. 批量过滤列表:fnmatch.filter(names, pattern)
filtered = fnmatch.filter(["a.py", "b.txt"], "*.py")
# 2. 单项判定(大小写敏感度取决于操作系统):fnmatch.fnmatch(name, pattern)
is_py = fnmatch.fnmatch(filename, "*.py")
# 3. 严格区分大小写的单项判定:fnmatch.fnmatchcase(name, pattern)
is_py_case = fnmatch.fnmatchcase(filename, "*.py") # 恒为 False
# 4. 查看转换后的底层正则表达式:fnmatch.translate(pattern)
regex_str = fnmatch.translate("*.py")
print("转换后的正则表达式:", regex_str)
四、容易混淆的常见说法与深度辨析
说法辨析 1:“过滤文件名一定要用 glob 模块”
这种说法在初学者中十分常见,但它混淆了“文件系统遍历”与“纯内存字符串匹配”的职责边界。
- 何时成立:当你的目标是从物理硬盘的某个文件夹中扫描出存在的文件时,使用
glob.glob()或pathlib.Path.glob()确实一步到位。 - 为何需要修正:很多场景下文件名列表早已存在于内存中。例如:从压缩包中读取
zip_file.namelist()、从 FTP/S3 接口拉取对象键列表、或对已有的过滤中间结果进行二次提炼。此时如果为了用glob而把列表写入磁盘或尝试拼接假路径,不仅执行效率低下,而且会带来不必要的 I/O 开销与错误风险。fnmatch专注于对纯字符串列表进行模式匹配,是纯粹的无状态运算。
说法辨析 2:“fnmatch 的匹配结果在不同操作系统上完全一致”
这是导致线上环境出现诡异 Bug 的典型原因之一。
- 看似合理的原因:Python 标准库多数模块具有优良的跨平台抽象,读者往往默认字符串匹配行为也是跨平台中立的。
- 实际机制与修正:
fnmatch.fnmatch()和fnmatch.filter()内部会调用os.path.normcase()。在 Windows 和 macOS(部分文件系统)上,文件名是不区分大小写的,因此*.PY能够成功匹配app.py;但在 Linux 服务器上,文件系统严格区分大小写,*.PY将无法匹配app.py。 - 防坑做法:如果项目需要在 Linux 服务器与 Windows 本地开发机之间保持一致的严格匹配,应显式采用
fnmatch.fnmatchcase(),例如:
[f for f in file_names if fnmatch.fnmatchcase(f, "*.py")]
五、实战技巧:多模式组合与复杂过滤
在实际业务中,过滤需求往往不是单个后缀那么单一,例如需要“同时匹配所有的 png 和 jpg 文件”,或者“排除所有备份文件”。以下是处理这类需求的实用做法:
1. 匹配多种文件扩展名
fnmatch 原生不支持 *.(png|jpg) 这种正则中的管道分支。要实现多条件筛选,最清晰的做法是循环匹配并利用集合去重:
import fnmatch
all_files = [
"banner.PNG", "avatar.jpg", "script.py",
"vector.svg", "photo.jpeg", "data.json"
]
patterns = ["*.jpg", "*.jpeg", "*.png"]
matched_files = []
for pat in patterns:
# 结合大小写归一化
matched_files.extend(fnmatch.filter(all_files, pat))
# 保持顺序并去重
unique_matches = list(dict.fromkeys(matched_files))
print("图片资源列表:", unique_matches)
2. 包含与排除组合过滤
先通过白名单模式筛选候选集,再通过黑名单模式剔除临时或测试文件:
import fnmatch
source_files = [
"service.py", "test_service.py", "utils.py",
"test_utils.py", "models.py", "temp_models.py"
]
# 第一步:提取全部 Python 文件
candidates = fnmatch.filter(source_files, "*.py")
# 第二步:排除 test_ 开头与 temp_ 开头的文件
excluded = set(fnmatch.filter(candidates, "test_*")) | set(fnmatch.filter(candidates, "temp_*"))
production_files = [f for f in candidates if f not in excluded]
print("正式生产代码:", production_files)
# 输出: ['service.py', 'utils.py', 'models.py']
六、知识总结与避坑检查清单
为了方便在日后开发中快速参考与排查,这里将使用 fnmatch 过滤文件名时的核心准则提炼为如下自查卡片:
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









