Самостоятельно написанный os.walk-like гораздо медленнее самого os.walk - почему? - PullRequest
0 голосов
/ 28 января 2019

К сожалению, этот код работает медленнее, чем "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)

Ответы [ 2 ]

0 голосов
/ 29 января 2019

Этот код работает почти так же быстро, как 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)
0 голосов
/ 28 января 2019

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, кроме обработчика, заставляет замолчать.

Далее вы должны избавиться от глобальных переменных и использовать правильные имена переменных.files и dirs будут гораздо более понятными именами, чем y и z.Поскольку вы сделали глобалы y и z, вы сохраняете все имена файлов и каталогов для данного уровня, и для каждого первого включенного подкаталога вы затем повторно сообщаете те же самые имена файлов и каталогов, как будто они являются членами этихподкаталоги.Только после достижения первого листа такого дерева каталогов (без дополнительных подкаталогов) выполняются вызовы .clear() для y и z, что приводит к очень запутанным результатам с повторяющимися именами файлов.

Вы можете изучить исходный код 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 повторно используется для рекурсии в подкаталоги.

...