Построить дерево из списка путей к файлам ОС (Python) - зависит от производительности

Эй, я работаю над очень высокопроизводительным набором инструментов для управления файлами/анализа, написанным на python. Я хочу создать функцию, которая дает мне список или что-то подобное в виде дерева. Что-то вроде этого вопрос (связанный с java)

From:

dir/file
dir/dir2/file2
dir/file3
dir3/file4
dir3/file5

Примечание: список путей несортирован

в:

dir/
    file
    dir2/
        file2
    file3
dir3/
    file4
    file5

[[dir, [file, [dir2, [file2]], file3]], [dir3, [file4, file5]]]

что-то в этом роде. Я играл с некоторыми идеями, но ни одна из них не обеспечила скорость, которую я хотел бы иметь.

Примечание: у меня уже есть список путей, поэтому не беспокойтесь об этом. Функция принимает список путей и дает список дерева.

спасибо заранее

3 ответов


теперь, когда вы прояснили вопрос немного больше, я думаю, следующее, что вы хотите:

from collections import defaultdict

input_ = '''dir/file
dir/dir2/file2
dir/file3
dir2/alpha/beta/gamma/delta
dir2/alpha/beta/gamma/delta/
dir3/file4
dir3/file5'''

FILE_MARKER = '<files>'

def attach(branch, trunk):
    '''
    Insert a branch of directories on its trunk.
    '''
    parts = branch.split('/', 1)
    if len(parts) == 1:  # branch is a file
        trunk[FILE_MARKER].append(parts[0])
    else:
        node, others = parts
        if node not in trunk:
            trunk[node] = defaultdict(dict, ((FILE_MARKER, []),))
        attach(others, trunk[node])

def prettify(d, indent=0):
    '''
    Print the file tree structure with proper indentation.
    '''
    for key, value in d.iteritems():
        if key == FILE_MARKER:
            if value:
                print '  ' * indent + str(value)
        else:
            print '  ' * indent + str(key)
            if isinstance(value, dict):
                prettify(value, indent+1)
            else:
                print '  ' * (indent+1) + str(value)



main_dict = defaultdict(dict, ((FILE_MARKER, []),))
for line in input_.split('\n'):
    attach(line, main_dict)

prettify(main_dict)

выдает:

dir3
  ['file4', 'file5']
dir2
  alpha
    beta
      gamma
        ['delta']
        delta
          ['']
dir
  dir2
    ['file2']
  ['file', 'file3']

несколько вещей, чтобы отметить:

  • скрипт широко использует defaultdicts, в основном это позволяет пропустить проверку на наличие ключа и его инициализацию, если это не было
  • имена каталогов сопоставляются с ключами словаря, я подумал, что это может быть хорошей функцией для вас, так как ключ хэшируется, и вы сможете получить информацию намного быстрее, чем со списками. Вы можете получить доступ к иерархии в форме main_dict['dir2']['alpha']['beta']...
  • обратите внимание на разницу между .../delta и .../delta/. Я думал, что это полезно для вас, чтобы иметь возможность быстро различаться между вашим листом, являющимся каталогом или файлом.

Я надеюсь, что это ответ на ваш вопрос. Если что-то неясно, опубликуйте комментарий.


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

Что касается проблем с производительностью, вы рассматривали использование Pypy, Cython или Shedskin? У меня есть дедуплицирующая система резервного копирования, над которой я работал для удовольствия, которая может запускать тот же код на Pypy или Cython; запуск его на Pypy фактически превосходит версию Cython-augmented (много на 32 бит, немного на 64 бит). Я бы тоже хотел сравнить shedskin, но он, по-видимому, не может уступить границу shedskin/cpython.

кроме того, профилирование является de rigueur, когда у вас есть проблемы с производительностью - по крайней мере, если вы уже выбрали соответствующий алгоритм.


во-первых, "очень высокой производительности" и "питон" не хорошо перемешать. Если вы ищете оптимизацию производительности до крайности, переход на C принесет вам преимущества, намного превосходящие любую оптимизацию интеллектуального кода, о которой вы можете подумать.

во-вторых, трудно поверить, что узкое место на "управление файлами/анализ инструментария" будет эта функция. Операции ввода-вывода диск по крайней мере на несколько порядков медленнее, чем все, что происходит в памяти. Профилирование кода-единственный точный способ оценить это, но... Я готов заплатить тебе пиццу, если ошибаюсь! ;)

Я построил глупую тестовую функцию, чтобы выполнить некоторые предварительные измерения:

from timeit import Timer as T

PLIST = [['dir', ['file', ['dir2', ['file2']], 'file3']], ['dir3', ['file4', 'file5', 'file6', 'file7']]]

def tree(plist, indent=0):
    level = []
    for el in plist:
        if isinstance(el, list):
            level.extend(tree(el, indent + 2))
        else:
            level.append(' ' * indent + el)
    return level

print T(lambda : tree(PLIST)).repeat(number=100000)

вот результаты:

[1.0135619640350342, 1.0107290744781494, 1.0090651512145996]

поскольку список тестовых путей составляет 10 файлов, а количество итераций-100000, это означает, что за 1 секунду вы можете обработать дерево около 1 миллион файлов. Сейчас... если вы не работаете в Google, это кажется мне приемлемым результатом.

напротив, когда я начал писать этот ответ, я нажал на опцию "свойство" в корне моего основного 80Gb HD [это должно дать мне количество файлов на нем, используя код C]. Несколько минут прошло, и я нахожусь в 50 GB, 300000 файлов...

HTH! :)