关于python:自编写的os.walk-like比os.walk本身要慢得多 – 为什么?

Self written os.walk-alike is much slower then os.walk itself - why?

不幸的是,这个代码比"os.walk"运行得慢,但是为什么呢?

是"for"循环导致它运行缓慢吗?

"类似于"os.walk"的代码:("os.walk"函数做它做的事情)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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秒钟后结束:

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

0.5秒后:

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


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

Using scandir() instead of listdir() can significantly increase the performance of code that also needs file type or file attribute information, because os.DirEntry objects expose this information if the operating system provides it when scanning a directory. All os.DirEntry methods may perform a system call, but is_dir() and is_file() usually only require a system call for symbolic links; os.DirEntry.stat() always requires a system call on Unix but only requires one for symbolic links on Windows.

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

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

var为空(没有更多的子目录)时,您也会再次出现,导致您首先将空列表包装在另一个列表中,之后os.listdir()抛出TypeError异常,您的总括口袋妖怪会捕获它们,除了处理程序的沉默。

接下来,您应该去掉全局变量,并使用适当的变量名。filesdirs的名字要比yz清楚得多。因为您生成了yz全局变量,所以您将保留给定级别的所有文件和目录名,并且对于向下打开的每个第一个子目录,然后重新报告这些相同的文件和目录名,就像它们是这些子目录的成员一样。只有当到达这样一个目录树的第一个叶子(没有更多的子目录)时,才会执行对yz.clear()调用,从而导致重复文件名产生非常混乱的结果。

您可以研究os.walk()源代码,但如果我们将其简化为只使用自上而下的遍历,而不进行错误处理,则可以归结为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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变量被重新用于递归到子目录中。


这段代码几乎和os.walk一样快!

1
2
3
4
5
6
7
8
9
10
11
12
import os, time
from os.path import *

def walk(top):
    x = top;y=[];z=[]
    try:
        for i in os.listdir(top):
            y.append(i) if isdir(top+os.sep+i) else z.append(i)
    except: pass
    else:
        yield x,y,z
        for q in y: yield from walk(top+os.sep+q)