1. 为什么需要遍历文件夹?
在日常开发中,处理文件系统是Python程序员经常遇到的任务。无论是批量重命名文件、统计文件数量、搜索特定文件,还是进行数据分析前的数据收集,都需要先获取目标文件夹下的所有文件列表。
我遇到过很多开发者还在手动记录文件路径,或者只会处理单层目录。实际上Python提供了多种高效的文件遍历方法,掌握这些技巧可以让你在处理文件系统时事半功倍。
2. 基础方法:os模块的walk和listdir
2.1 os.listdir() - 最简单的文件列表获取
os.listdir()是Python中最基础的文件列表获取方法,它返回指定路径下的所有文件和子目录名称列表:
import os path = '/path/to/your/directory' files = os.listdir(path) print(files)这个方法有几个特点需要注意:
- 只返回名称,不包含完整路径
- 包含文件和目录,不进行区分
- 不递归子目录
- 返回顺序取决于文件系统,通常是无序的
提示:如果需要完整路径,可以使用os.path.join()拼接:
full_paths = [os.path.join(path, f) for f in files]
2.2 os.walk() - 递归遍历的金牌工具
os.walk()是Python中递归遍历目录树的"瑞士军刀",它会生成一个三元组(dirpath, dirnames, filenames):
for root, dirs, files in os.walk('/path/to/your/directory'): print(f"当前目录:{root}") print(f"子目录:{dirs}") print(f"文件:{files}")实际应用中,我经常这样使用:
file_list = [] for root, _, files in os.walk('/path'): for file in files: file_list.append(os.path.join(root, file))os.walk()有几个实用特性:
- topdown参数控制遍历顺序(默认True,从上到下)
- 可以原地修改dirs列表来控制遍历哪些子目录
- 遵循符号链接(除非设置followlinks=False)
3. 现代方法:pathlib的优雅实现
Python 3.4引入的pathlib模块提供了更面向对象的文件系统操作方式,代码通常更简洁易读。
3.1 Path.glob() - 模式匹配文件查找
from pathlib import Path # 查找所有.py文件(包括子目录) py_files = list(Path('.').rglob('*.py')) # 只查找当前目录的.txt文件 txt_files = list(Path('.').glob('*.txt'))rglob相当于glob的递归版本,非常方便。pathlib的glob支持这些模式:
*匹配任意字符?匹配单个字符[]匹配字符范围**递归匹配(当用在模式中间时)
3.2 Path.iterdir() - 更现代的listdir替代
directory = Path('/path/to/dir') for item in directory.iterdir(): if item.is_file(): print(f"文件:{item.name}") elif item.is_dir(): print(f"目录:{item.name}")相比os.listdir(),iterdir()的优势在于:
- 返回Path对象而非字符串
- 可以直接调用.is_file()/.is_dir()等方法
- 更符合现代Python风格
4. 高级技巧与性能优化
4.1 处理大目录时的性能考量
当处理包含数十万文件的目录时,性能变得很重要。以下是一些实测有效的优化技巧:
使用os.scandir()代替os.listdir()
with os.scandir('/path') as entries: for entry in entries: if entry.is_file(): print(entry.name)scandir()在Windows和Linux上性能更好,因为它不需要为每个文件单独调用stat()。
并行处理文件使用多线程/多进程处理不同子目录:
from concurrent.futures import ThreadPoolExecutor def process_file(file_path): # 处理单个文件 pass with ThreadPoolExecutor() as executor: for root, _, files in os.walk('/path'): executor.map(process_file, [os.path.join(root, f) for f in files])
4.2 过滤与排除技巧
实际项目中,我们通常需要过滤特定文件:
# 只获取图片文件 image_exts = {'.jpg', '.png', '.gif'} image_files = [ f for f in Path('.').rglob('*') if f.suffix.lower() in image_exts ] # 排除隐藏文件和目录 visible_files = [ f for f in Path('.').rglob('*') if not any(part.startswith('.') for part in f.parts) ]4.3 处理特殊文件名
有些文件名可能包含特殊字符或编码问题,正确处理很重要:
# 安全地处理可能包含特殊字符的路径 try: with open(filename, 'r', encoding='utf-8') as f: content = f.read() except UnicodeDecodeError: # 尝试其他编码 with open(filename, 'r', encoding='latin-1') as f: content = f.read()5. 常见问题与解决方案
5.1 权限问题处理
遍历系统目录时经常会遇到权限错误,正确处理方式:
for root, dirs, files in os.walk('/'): try: # 正常处理文件 pass except PermissionError: print(f"无法访问 {root},跳过...") dirs[:] = [] # 跳过这个目录的所有子目录 continue5.2 符号链接处理
默认情况下,os.walk()会跟随符号链接,可能导致无限循环:
for root, dirs, files in os.walk('/path', followlinks=False): # 处理文件 pass5.3 内存优化
对于超大目录,避免一次性加载所有文件到内存:
def process_large_dir(path): for root, _, files in os.walk(path): for file in files: filepath = os.path.join(root, file) # 立即处理文件,不保存到列表 process_file(filepath)5.4 跨平台兼容性
不同操作系统路径分隔符不同,正确处理方式:
# 错误方式(Windows不兼容) path = 'folder/file.txt' # 正确方式 path = os.path.join('folder', 'file.txt') # 或者 path = Path('folder') / 'file.txt'6. 实际应用案例
6.1 项目源代码统计
统计项目中各种语言的代码行数:
def count_lines(filepath): with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: return sum(1 for _ in f) stats = {} for ext in ['.py', '.js', '.html']: files = Path('.').rglob(f'*{ext}') stats[ext] = sum(count_lines(f) for f in files if f.is_file())6.2 批量重命名文件
为目录下所有图片添加前缀:
for i, img in enumerate(Path('photos').glob('*.jpg'), 1): new_name = f"vacation_{i:03d}{img.suffix}" img.rename(img.with_name(new_name))6.3 查找重复文件
通过MD5哈希查找重复文件:
import hashlib def file_hash(filepath): hasher = hashlib.md5() with open(filepath, 'rb') as f: for chunk in iter(lambda: f.read(4096), b''): hasher.update(chunk) return hasher.hexdigest() file_hashes = {} for file in Path('.').rglob('*'): if file.is_file(): h = file_hash(file) file_hashes.setdefault(h, []).append(file)7. 性能对比与选择建议
不同方法的性能特点:
| 方法 | 特点 | 适用场景 |
|---|---|---|
| os.listdir() | 基础,只返回名称 | 简单列出当前目录内容 |
| os.scandir() | 性能更好,返回更多信息 | 需要文件类型等元信息 |
| os.walk() | 递归遍历,功能全面 | 需要处理整个目录树 |
| Path.glob() | 模式匹配,代码简洁 | 需要按模式查找文件 |
| Path.rglob() | 递归模式匹配 | 需要递归查找特定模式文件 |
选择建议:
- 简单需求:优先考虑pathlib,代码更简洁
- 性能关键:考虑os.scandir()和并行处理
- 复杂过滤:结合生成器表达式和条件判断
- 跨平台:使用pathlib或os.path.join
8. 个人经验分享
在实际项目中,我总结出几个有用的经验:
路径处理统一使用pathlib:自从Python 3.6后,pathlib性能已经足够好,而且代码更清晰。我几乎不再直接使用os.path模块。
尽早处理异常:文件系统操作充满不确定性(文件被删除、权限变化等),在遍历开始时就要考虑异常处理。
使用生成器节省内存:对于大目录,使用生成器表达式而不是列表推导式可以显著减少内存使用:
# 内存友好方式 py_files = (p for p in Path('.').rglob('*.py') if p.is_file())记录遍历进度:长时间运行的遍历任务应该记录进度:
for i, file in enumerate(Path('.').rglob('*'), 1): if i % 100 == 0: print(f"已处理 {i} 个文件...")考虑文件名编码:特别是在跨平台项目中,Linux和Windows对文件名编码处理不同,建议统一使用utf-8并做好异常处理。