如何将计算文件夹信息的Python非尾递归函数转为非递归实现
问题描述
我写了一个递归函数dirSize,可以获取磁盘或指定文件夹的总大小、文件数及子目录数,实现简洁且运行正常:
# 将python的dirSize函数转为非递归版本: from pathlib import Path obj = {} def dirSize(dir: Path, size = 0, files = 0, dirs = 0): for sub_dir in dir.glob('*'): if sub_dir.is_dir(): dirs += 1 res_size, res_files, res_dirs = dirSize(sub_dir) size += res_size files += res_files dirs += res_dirs elif sub_dir.is_file(): files += 1 size += sub_dir.stat().st_size obj[dir.as_posix()] = { 'size': size, 'files': files, 'dirs': dirs, } return size, files, dirs dirSize(Path('~/Documents').expanduser()) # 上面是测试代码,最终代码需要在管理员权限的cmd中运行: #dirSize(Path('C:/')) with open(Path('~/Desktop/results.tsv').expanduser().as_posix(), 'w') as file: file.write('Directory\tBytes\tFiles\tDirs\n') for key in sorted(obj): dir = obj[key] file.write(key) file.write('\t') file.write(str(dir['size'])) file.write('\t') file.write(str(dir['files'])) file.write('\t') file.write(str(dir['dirs'])) file.write('\n')
但老板认为不是所有开发者都能熟练使用递归,要求改成非递归版本。我尝试用AI转换,但AI忽略了代码中size += res_size、files += res_files、dirs += res_dirs这几行的自底向上求和逻辑。之后我试过用栈循环(类似Dijkstra算法的思路),但没找到简单的自底向上求和方案;考虑过给obj变量添加层级/父/子属性,在队列处理完后再做自底向上求和,但这种方式太繁琐。有没有更简便的实现方法?
补充说明
- 为何不用
os.walk?os.walk虽然能直接消除递归,但无法实现自底向上求和,而我们需要的报告要展示每个目录的子目录数、磁盘占用空间及文件数。 - 递归函数有什么问题?
递归函数运行完全正常,只是老板要求提供非递归版本,所以我在纠结怎么写出简洁的非递归方案。
解决方案
可以用栈+状态标记的方式模拟递归的自底向上处理逻辑,核心思路是通过栈区分目录的「待处理子项」和「待汇总数据」两个阶段,先处理所有子目录/文件,再汇总父目录的统计数据,完美复刻原递归的逻辑:
from pathlib import Path def non_recursive_dir_size(root_dir: Path): obj = {} # 栈元素:(目录路径, 是否已处理子项) stack = [(root_dir.expanduser(), False)] while stack: current_dir, is_processed = stack.pop() if not is_processed: # 第一次弹出,先标记为待汇总状态重新入栈 stack.append((current_dir, True)) # 逆序入栈所有子项(栈是后进先出,保证处理顺序和原递归一致) for item in reversed(list(current_dir.glob('*'))): if item.is_dir(): stack.append((item, False)) else: # 文件无后续子项,直接记录基础统计数据 obj[item.as_posix()] = { 'size': item.stat().st_size, 'files': 1, 'dirs': 0 } else: # 已处理完所有子项,开始汇总数据 total_size = 0 total_files = 0 total_dirs = 0 for item in current_dir.glob('*'): item_path = item.as_posix() if item.is_dir(): total_dirs += 1 total_size += obj[item_path]['size'] total_files += obj[item_path]['files'] total_dirs += obj[item_path]['dirs'] else: total_files += obj[item_path]['files'] total_size += obj[item_path]['size'] obj[current_dir.as_posix()] = { 'size': total_size, 'files': total_files, 'dirs': total_dirs } return obj # 测试运行 result_obj = non_recursive_dir_size(Path('~/Documents')) # 最终生产环境运行(需管理员权限): # result_obj = non_recursive_dir_size(Path('C:/')) # 写入TSV报告 with open(Path('~/Desktop/results.tsv').expanduser(), 'w') as file: file.write('Directory\tBytes\tFiles\tDirs\n') for dir_path in sorted(result_obj.keys()): stats = result_obj[dir_path] file.write(f"{dir_path}\t{stats['size']}\t{stats['files']}\t{stats['dirs']}\n")
代码说明
- 状态标记机制:通过
is_processed标记,确保先处理所有子目录和文件,再回到父目录进行数据汇总,完全匹配原递归的自底向上逻辑 - 逆序入栈:利用栈的后进先出特性,逆序添加子项可以保证处理顺序和原递归一致,避免统计结果出现偏差
- 文件直接记录:文件没有子项,第一次遍历就写入统计数据,减少重复处理步骤
- 数据汇总逻辑:处理待汇总的目录时,遍历所有子项累加数据,和原递归中
size += res_size等代码的逻辑完全对应
内容的提问来源于stack exchange,提问作者Marquinho Peli
相关产品推荐
相关产品推荐

