首页 > 解决方案 > 自写的 os.walk-alike 比 os.walk 本身慢得多 - 为什么?

问题描述

不幸的是,这段代码的运行速度比“os.walk”慢,但为什么呢?

会不会是“for”循环导致它运行缓慢?

“像 'os.walk' 一样工作的代码:( “os.walk” 函数完成了它的工作)

注意: 我写信是为了提高自己!

import os, time
from os.path import *

x = ""
y = []
z = []
var = 0

def walk(xew):
    global top, var, x,y,z
    if not var: var = [xew]
    for i in var:
        try:
            for ii in os.listdir(i):
                y.append(ii) if isdir(i+os.sep+ii) else z.append(ii)

            x = top = i
            var = [top+os.sep+i for i in os.listdir(top) if isdir(top+os.sep+i)]         
        except:
            continue
        yield x,y,z
        yield from walk(var)
        var.clear();y.clear();z.clear()

例如

它在 2 秒后结束:

for x,y,z in walk(path):
    print(x)

它在 0.5 秒内:

for x,y,z in os.walk(path):
    print(x)

标签: pythonpython-3.xperformancepython-3.6

解决方案


os.walk()不使用os.listdir()。它使用了更快的os.scandir()函数,它为每个目录条目提供了一个迭代器更多的信息:

使用scandir()而不是listdir()可以显着提高还需要文件类型或文件属性信息的代码的性能,因为os.DirEntry如果操作系统在扫描目录时提供了这些信息,对象就会公开这些信息。所有os.DirEntry方法都可以执行系统调用,但is_dir()通常is_file()只需要对符号链接进行系统调用;os.DirEntry.stat()在 Unix 上总是需要一个系统调用,但在 Windows 上只需要一个用于符号链接。

os.walk()代码大量使用了DirEntry.is_dir()调用,这os.scandir()比使用便宜得多os.isdir()(必须进行单独的os.stat()调用)。

接下来,您的代码调用os.isdir()过于频繁。您实际上为路径中的每个文件条目调用了两次。您已经收集了 中的所有子目录y,重新创建时无需再次测试路径var。这些额外的isdir()电话会花费您很多时间。

您还递归何时var(没有其他子目录),导致您首先将空列表包装在另一个列表中,然后os.listdir()引发TypeError异常,您的毯子 Pokemon-catch-em-all 除了处理程序静音。

接下来,您应该摆脱全局变量,并使用正确的变量名称。filesanddirs将比yand更清晰的名称z。因为您创建了全局变量y,所以z您保留了给定级别的所有文件和目录名称,并且对于向下的每个第一个子目录,您然后重新报告这些相同的文件和目录名称,就好像它们是这些子目录的成员一样。只有当到达这样一个目录树的第一个叶子(没有更多的子目录)时,才会.clear()调用yz执行,导致重复文件名的结果非常混乱。

你可以研究os.walk()源码,但是如果我们把它简化为只使用自上而下的遍历而没有错误处理,那么它归结为:

def walk(top):
    dirs = []
    nondirs = []

    with os.scandir(top) as scandir_it:
        for entry in scandir_it:
            if entry.is_dir():
                dirs.append(entry.name)
            else:
                nondirs.append(entry.name)

    yield top, dirs, nondirs

    for dirname in dirs:
        new_path = os.path.join(top, dirname) 
        yield from walk(new_path)

请注意,没有使用全局变量;在这个算法中根本不需要任何东西。每个目录只有一次os.scandir()调用,并且该dirs变量被重新用于递归到子目录中。


推荐阅读