Python 自编的os.walk-like比os.walk本身慢得多——为什么?

Python 自编的os.walk-like比os.walk本身慢得多——为什么?,python,python-3.x,performance,python-3.6,Python,Python 3.x,Performance,Python 3.6,不幸的是,这段代码比“os.walk”运行得慢,但为什么呢 是“for”循环导致它运行缓慢吗 类似于“os.walk”的代码:(“os.walk”函数完成它的功能) 注意:我写信是为了提高自己!: 例如: 2秒后结束: 在0.5秒内: os.walk()不使用os.listdir()。它使用的速度更快,这为每个目录条目提供了一个迭代器,其中包含更多信息: 使用scandir()而不是listdir()可以显著提高还需要文件类型或文件属性信息的代码的性能,因为os.DirEntry对象在扫描目录时

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

是“for”循环导致它运行缓慢吗

类似于“os.walk”的代码:(“os.walk”函数完成它的功能)

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

例如

2秒后结束:

在0.5秒内:

os.walk()
不使用
os.listdir()
。它使用的速度更快,这为每个目录条目提供了一个迭代器,其中包含更多信息:

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

os.walk()
代码大量使用了
DirEntry.is\u dir()
调用,使用
os.scandir()
比使用
os.isdir()
要便宜得多(必须单独调用
os.stat()

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

var
为空(没有其他子目录)时,您也会递归,导致您首先将空列表包装到另一个列表中,然后
os.listdir()
抛出一个
TypeError
异常,您的毛毯口袋妖怪会捕获除处理程序沉默之外的所有em

接下来,您应该去掉全局变量,并使用适当的变量名。
文件
目录
的名称将比
y
z
清晰得多。因为您创建了
y
z
全局变量,所以您保留了给定级别和每个第一个子目录的所有文件和目录名在down上,您将重新报告这些相同的文件和目录名,就像它们是这些子目录的成员一样。只有在到达此类目录树(没有其他子目录)的第一个叶时,才执行
.clear()
执行对
y
z
的调用,导致重复文件名的结果非常混乱

您可以研究,但如果我们将其简化为只使用自顶向下遍历,而不使用错误处理,则可以归结为:

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
一样快

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)

除了实现细节之外,如果这会影响performance@mfrackowiak:然而,这并不能解释大的差异。我猜只要花
os.walk
半秒钟走一走,就会形成一个较大的目录树。可能有一些符号链接。OPs实现遵循符号链接目录而
os
中的版本在默认情况下并不是这样。如果我没有弄错的话,将其更改为使用
os.scandir
(这是Giampaolo Rodolá最近的作品)性能提高了20-50%,系统调用数量减少了差不多,但是OP的性能差异达到了200%。所以这不是唯一的原因,尽管这是一个重要的因素。@GiacomoAlzetta:这取决于文件的数量和递归到w中的子目录的数量嗯。我们不知道这里的混合。是的,你是对的。OP应该在同一个目录下测试两个不同python版本(例如python2.7和python3.7)的os.walk看看他们是否看到了显著的差异……如果没有巨大的差异,那么另一个因素在起作用,如果差异很大,那么很可能只是对操作系统的改变覆盖剩余时差的呼叫。@MartijnPieters感谢您的回答,我的目的是理解为什么这个代码很慢,感谢您,我学到了一些让自己满意的东西!
for x,y,z in os.walk(path):
    print(x)
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)
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)