Python os.walk 函数
上次修改时间:2025 年 4 月 11 日
本综合指南探讨了 Python 的 os.walk 函数,该函数递归地遍历目录树。我们将涵盖目录导航、文件列表和实际的文件系统探索示例。
基本定义
os.walk 函数通过自上而下或自下而上的方式生成目录树中的文件名。它为访问的每个目录返回一个 3 元组。
主要参数:top(根目录)、topdown(遍历顺序)、onerror(错误处理程序)、followlinks(跟随符号链接)。 返回 (dirpath, dirnames, filenames) 元组。
基本目录遍历
os.walk 的最简单用法是列出从给定根目录开始的所有文件和目录。 此示例显示了基本的递归遍历。
import os
# Walk through directory tree
for root, dirs, files in os.walk("my_directory"):
print(f"Current directory: {root}")
print(f"Subdirectories: {dirs}")
print(f"Files: {files}")
print("-" * 40)
# Count total files
file_count = sum(len(files) for _, _, files in os.walk("my_directory"))
print(f"Total files found: {file_count}")
此示例显示了 os.walk 用法的基本结构。 对于每个目录,它都会打印路径、子目录和文件。 最后,它会计算所有文件。
生成器会产生包含当前路径、直接子目录以及在遍历的每个级别上的非目录文件的元组。
按扩展名筛选文件
我们可以在遍历期间过滤文件,仅处理特定文件类型。 此示例查找目录树中的所有 Python 文件 (.py)。
import os
# Find all Python files
python_files = []
for root, _, files in os.walk("src"):
for file in files:
if file.endswith(".py"):
python_files.append(os.path.join(root, file))
print("Python files found:")
for file in python_files:
print(f"- {file}")
# Count lines of code
total_lines = 0
for file in python_files:
with open(file) as f:
total_lines += len(f.readlines())
print(f"\nTotal lines of Python code: {total_lines}")
此代码首先收集所有 Python 文件,然后计算它们的总行数。 os.path.join 确保跨操作系统的正确路径构造。
下划线 (_) 忽略 dirnames,因为我们在此示例中不需要它们。 这是 Python 中未使用变量的常见约定。
修改目录遍历
可以就地修改 dirnames 列表,以控制访问哪些子目录。 此示例排除以点开头的目录。
import os
# Skip hidden directories (starting with dot)
for root, dirs, files in os.walk("project"):
# Modify dirs in-place to skip hidden directories
dirs[:] = [d for d in dirs if not d.startswith(".")]
print(f"Scanning: {root}")
for file in files:
if not file.startswith("."): # Skip hidden files too
print(f" - {file}")
# Alternative: Skip specific directories entirely
skip_dirs = {"venv", "__pycache__", "node_modules"}
for root, dirs, files in os.walk("project"):
dirs[:] = [d for d in dirs if d not in skip_dirs]
# Process remaining files...
通过在遍历期间修改 dirs 列表,我们可以控制递归。 这比在遍历完成后进行过滤更有效。
切片赋值 (dirs[:]) 就地修改列表,这会影响 walk 行为。 这是 os.walk 的一个关键特性。
计算目录大小
我们可以使用 os.walk 来计算目录树中所有文件的总大小。 此示例显示了每个目录和总大小的计算。
import os
def get_size(start_path):
total_size = 0
for dirpath, _, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
try:
total_size += os.path.getsize(fp)
except OSError:
continue
return total_size
# Calculate size for each directory
for root, dirs, _ in os.walk("data"):
dir_sizes = {}
for d in dirs:
path = os.path.join(root, d)
size = get_size(path)
dir_sizes[d] = size
print(f"\nDirectory: {root}")
for d, size in dir_sizes.items():
print(f" {d}: {size/1024:.2f} KB")
# Total size
total = get_size("data")
print(f"\nTotal data size: {total/1024/1024:.2f} MB")
此示例显示了两种方法:递归计算总大小和显示每个目录的细分。 错误处理可防止因无法访问的文件而崩溃。
该函数将字节转换为 KB 和 MB,以提高可读性。 实际应用程序可能会添加更多格式或阈值检查。
查找重复文件
通过将 os.walk 与文件哈希相结合,我们可以识别目录结构中的重复文件。 此示例使用 MD5 哈希进行比较。
import os
import hashlib
def file_hash(filepath):
hasher = hashlib.md5()
with open(filepath, "rb") as f:
while chunk := f.read(8192):
hasher.update(chunk)
return hasher.hexdigest()
# Find duplicate files
file_hashes = {}
duplicates = []
for root, _, files in os.walk("photos"):
for file in files:
path = os.path.join(root, file)
try:
file_size = os.path.getsize(path)
if file_size > 0: # Skip empty files
fhash = file_hash(path)
if fhash in file_hashes:
duplicates.append((path, file_hashes[fhash]))
else:
file_hashes[fhash] = path
except (OSError, PermissionError):
continue
print("Duplicate files found:")
for dup, original in duplicates:
print(f"{dup} is a duplicate of {original}")
此代码构建文件哈希字典并检查重复项。 它跳过空文件并优雅地处理潜在的权限错误。
对于大型文件集合,请考虑在哈希之前添加大小检查或使用更快的哈希算法(如 xxHash)以获得更好的性能。
按修改时间处理文件
此示例查找在过去 7 天内修改的文件,演示如何将 os.walk 与文件元数据操作相结合。
import os
import time
# Files modified in last 7 days
recent_files = []
current_time = time.time()
seven_days_ago = current_time - (7 * 24 * 60 * 60)
for root, _, files in os.walk("logs"):
for file in files:
path = os.path.join(root, file)
try:
mtime = os.path.getmtime(path)
if mtime > seven_days_ago:
recent_files.append((path, time.ctime(mtime)))
except OSError:
continue
print("Files modified in last 7 days:")
for path, mtime in sorted(recent_files, key=lambda x: x[1], reverse=True):
print(f"{mtime}: {path}")
# Archive old files
for root, _, files in os.walk("logs"):
for file in files:
path = os.path.join(root, file)
mtime = os.path.getmtime(path)
if mtime <= seven_days_ago:
# Add archiving logic here
print(f"Archiving: {path}")
该脚本首先识别最近的文件,然后显示如何分别处理较旧的文件。 时间计算使用自 epoch 以来的秒数进行比较。
此模式对于日志轮换、备份系统或任何基于时间的 文件处理任务都非常有用。
自下而上的目录遍历
设置 topdown=False 会反转遍历顺序,以便在其父目录之前处理子目录。 这对于目录删除等操作非常有用。
import os
import shutil
# Bottom-up traversal example
print("Bottom-up traversal order:")
for root, dirs, files in os.walk("temp", topdown=False):
print(f"Processing: {root}")
for file in files:
file_path = os.path.join(root, file)
print(f" Deleting file: {file_path}")
os.unlink(file_path)
# Now safe to remove the directory
print(f" Removing directory: {root}")
os.rmdir(root)
# Alternative using shutil for entire tree removal
if os.path.exists("temp_backup"):
print("\nRemoving backup directory with shutil:")
shutil.rmtree("temp_backup")
自下而上的方法确保我们在尝试删除其父目录之前处理文件。 该示例显示了手动和 shutil 方法。
当必须先完成对子项的操作,然后才能处理其父项时,自下而上的遍历至关重要。
安全注意事项
- 符号链接处理: followlinks=False 阻止跟随符号链接
- 权限错误: 实现 onerror 回调以进行优雅处理
- 路径清理: 始终使用 os.path.join 获取跨平台路径
- 内存使用: 对于大型目录,请考虑迭代方法
- 竞争条件: 文件系统在遍历期间可能会发生变化
最佳实践
- 使用生成器: 增量处理文件以节省内存
- 处理错误: 实现 onerror 以实现健壮的遍历
- 修改 dirs 列表: 通过就地编辑 dirs 来控制递归
- 跨平台: 使用 os.path 函数进行路径操作
- 记录行为: 注意遍历顺序(自上而下/自下而上)
资料来源
作者
列出所有 Python 教程。