在日常python编程中,有很多遍历文件夹内文件的需求,而os.walk方法就是一个满足该需求的例子。不熟悉这个方法的同学,刚开始用os.walk的时候难免踩坑。因此本文采用源码分析的方式,讲述os.walk的机理,让大家对于这个方法有更加深入的理解。
以python3为例,os.walk方法的源码如下:
1 | def walk(top, topdown=True, onerror=None, followlinks=False): |
os.walk包含了4个参数:top、topdown、onerror以及followlinks。top指代需要遍历的根目录;topdown指代是否自顶向下进行遍历;onerror为过程中抛异常的回调;followlinks指代是否需要跟踪符号链接。
从源码和注释中可以看到,os.walk本身返回的是一个生成器generator。生成器会根据上一次的状态,不断地生产下一个值,因此生成器生成的值可能会是无穷无尽的,但因为只保留少量的状态信息,所以不太耗费资源。想象一下,如果遍历一个包含大量文件的文件夹,不用生成器直接把遍历所有的结果整合起来给到调用者,那势必需要花费相当多的资源去存储所有结果的信息。每当遍历到一个文件夹时,os.walk会采取如下操作:
- 尝试获得os.scandir的iterator迭代器。scandir方法,返回的也是generator,和listdir不同。
- 在scandir_iterator作用域里,不断调用next获取文件夹下剩余的entry
- 判断entry是文件夹还是文件,分别放到dirs与nondirs列表中
- 如果不是topdown自顶向下,而是bottomup自底向上,需要将搜到的子文件夹/符号链接的文件夹放到walk_dirs中,后续先遍历它们
- 确定dirs跟nondirs列表后,根据是否自顶向下遍历来执行行为
- 如果设置了自顶向下遍历,就yield
(当前目录, 子一层的文件夹列表, 子一层的非文件夹列表)
。值得一提的是,我们可以修改子一层文件夹列表里面的值,来实现比如剪枝的需求 - 如果设置自底向上,就先yield from walk_dirs里的各个文件夹及子一层文件夹/非文件夹列表,然后再yield当前目录/子一层信息
- 如果设置了自顶向下遍历,就yield
os.walk的原理大致如此。与此同时,os.walk方法也是一个非常典型的使用generator的例子,值得我们在应用python的时候学习与回顾