Как получить прогресс os.walk в python? - PullRequest
11 голосов
/ 29 января 2010

У меня есть фрагмент кода, который я использую для поиска исполняемых файлов игровых файлов и возврата каталогов. Я действительно хотел бы получить какой-то индикатор прогресса относительно того, как далеко продвинулся os.walk. Как бы я совершил такую ​​вещь?

Я пытался сделать startpt = root.count(os.sep) и оценить это, но это просто показывает, насколько глубоко os.walk находится в дереве каталогов.

def locate(filelist, root=os.curdir): #Find a list of files, return directories.
    for path, dirs, files in os.walk(os.path.abspath(root)):
        for filename in returnMatches(filelist, [k.lower() for k in files]):
            yield path + "\\"

Ответы [ 10 ]

5 голосов
/ 29 января 2010

Это зависит!

Если файлы и каталоги распределяются более или менее равномерно, вы можете показать грубый процесс, предполагая, что каждый каталог верхнего уровня будет занимать одинаковое количество времени. Но если они не распределены равномерно, вы не можете узнать об этом дешево. Вы либо должны заранее знать, насколько заполнен каждый каталог, либо вам нужно дважды использовать os.walk для всего этого (но это полезно, только если ваша фактическая обработка занимает намного больше времени, чем сам os.walk).

То есть: скажем, у вас есть 4 каталога верхнего уровня, каждый из которых содержит 4 файла. Если вы предполагаете, что каждый каталог верхнего уровня занимает 25% прогресса, а каждый файл занимает еще 25% прогресса для этого каталога, вы можете показать хороший индикатор прогресса. Но если в последнем подкаталоге окажется гораздо больше файлов, чем в первых нескольких, ваш индикатор прогресса достигнет 75%, прежде чем вы узнаете об этом. Вы не можете исправить это, если само os.walk является узким местом (а не вашей обработкой), и это произвольное дерево каталогов (не то, где вы заранее знаете примерно, сколько времени займет каждое поддерево).

И, конечно, при условии, что стоимость каждого файла примерно одинакова ...

4 голосов
/ 29 января 2010

Я понял это.

Я использовал os.listdir для получения списка каталогов верхнего уровня, а затем использовал функцию .split для пути, возвращаемого os.walk, возвращая каталог первого уровня, в котором он находился в настоящее время.

Это оставило меня со списком каталогов верхнего уровня, в котором я мог найти индекс текущего каталога os.walk и сравнить возвращаемый индекс с длиной списка, что дало мне% завершения. ;)

Это не дает мне гладкого прогресса, потому что уровень работы, выполняемой в каждом каталоге, может варьироваться, но сглаживание индикатора прогресса меня не беспокоит. Но это может быть легко достигнуто путем расширения пути проверки глубже в структуру каталогов.

Вот окончательный код получения моего прогресса:

def locateGameDirs(filelist, root=os.curdir): #Find a list of files, return directories.
    toplevel = [folder for folder in os.listdir(root) if os.path.isdir(os.path.join(root, folder))] #List of top-level directories
    fileset = set(filelist)

    for path, dirs, files in os.walk(os.path.abspath(root)):

        curdir = path.split('\\')[1] #The directory os.walk is currently in.

        try: #Thrown here because there's a nonexistant(?) first entry.
            youarehere = toplevel.index(curdir)
            progress = int(((youarehere)/len(toplevel))*100)
        except:
            pass

        for filename in returnMatches(filelist, [k.lower() for k in files]):
            yield filename, path + "\\", progress

И сейчас для целей отладки я делаю это далее в коде:

    for wow in locateGameDirs(["wow.exe", "firefox.exe", "vlc.exe"], "C:\\"):
    print wow

Есть ли хороший маленький способ избавиться от этой попытки / кроме ?; кажется, первая итерация пути ничего мне не дает ...

4 голосов
/ 29 января 2010

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

2 голосов
/ 29 января 2010

Сделайте это за два прохода: сначала посчитайте, сколько всего файлов / папок в дереве, а затем во время второго прохода выполните фактическую обработку.

0 голосов
/ 30 января 2010

Ну, это было весело. Вот еще один глупый способ сделать это, но, как и все остальное, он рассчитывает только правильный прогресс для единообразных путей.

import os, sys, time

def calc_progress(progress, root, dirs):
    prog_start, prog_end, prog_slice = 0.0, 1.0, 1.0

    current_progress = 0.0
    parent_path, current_name = os.path.split(root)
    data = progress.get(parent_path)
    if data:
        prog_start, prog_end, subdirs = data
        i = subdirs.index(current_name)
        prog_slice = (prog_end - prog_start) / len(subdirs)
        current_progress = prog_slice * i + prog_start

        if i == (len(subdirs) - 1):
            del progress[parent_path]

    if dirs:
        progress[root] = (current_progress, current_progress+prog_slice, dirs)

    return current_progress

def walk(start_root):
    progress = {}
    print 'Starting with {start_root}'.format(**locals())

    for root, dirs, files in os.walk(start_root):
        print '{0}: {1:%}'.format(root[len(start_root)+1:], calc_progress(progress, root, dirs))
0 голосов
/ 29 января 2010

Одна оптимизация, которую вы могли бы сделать - вы конвертируете список файлов в набор при каждом вызове returnMatches, даже если он никогда не меняется. переместите преобразование в начало функции 'locate' и передайте набор в каждую итерацию.

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

Мышление из коробки здесь ... что, если вы сделали это на основе размер :

  • Используйте подпроцесс , чтобы запустить 'du -sb' и получить total_size вашего корневого каталога
  • Пока вы идете, проверяйте размер каждого файла и уменьшайте его от вашего total_size (давая вам оставшийся_size)
  • pct_complete = (total_size - остальные_size) / total_size

Мысли

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

Как я уже сказал в комментарии, горлышко бутылки производительности, вероятно, находится за пределами функции locate. ваш returnMatches довольно дорогая функция. Я думаю, вам лучше заменить его следующим кодом:

def locate(filelist, root=os.curdir)
    fileset = set(filelist)            # if possible, pass the set instead of the list as a first argument
    for path, dirs, files in os.walk(os.path.abspath(root)):
            if any(file.lower() in fileset for file in files):
                yield path + '\\'

Таким образом, вы уменьшаете количество расточительных операций, получаете один раз для каждого файла в каталоге (что, на мой взгляд, является тем, что вы на самом деле сделали с отступом), и вы можете в то же время забыть о прогрессе. Я не думаю, что прогресс будет ожидаемой особенностью интерфейса в любом случае.

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

Я предлагаю вам не ходить по каталогу. Вместо этого используйте приложение на основе индекса для быстрого поиска файлов. Вы можете использовать интерфейс командной строки приложения через подпроцесс и находить файлы практически мгновенно.

В Windows см. Все . В UNIX проверьте locate. Не уверен насчет Mac, но я уверен, что там тоже есть вариант.

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

Вам нужно знать общее количество файлов, чтобы сделать значимый индикатор прогресса.
Вы можете получить количество файлов, подобных этому

len(list(os.walk(os.path.abspath(root))))

но это займет некоторое время, и вам, вероятно, понадобится индикатор прогресса для этого ...

Чтобы быстро найти количество файлов, вам понадобится файловая система, которая отслеживает количество файлов для вас.

Возможно, вы можете сохранить итоги из предыдущего прогона и использовать их в качестве оценки

...