Исключая каталоги в os.walk

148

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

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

root--
     |
     --- dirA
     |
     --- dirB
     |
     --- uselessStuff --
                       |
                       --- moreJunk
                       |
                       --- yetMoreJunk

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

У кого-нибудь есть идея? Может быть, есть сторонняя библиотека, которая предоставляет что-то подобное?

antred
источник

Ответы:

243

Изменение dirs на месте приведет к удалению (последующих) файлов и каталогов, которые посещают os.walk:

# exclude = set([...])
for root, dirs, files in os.walk(top, topdown=True):
    dirs[:] = [d for d in dirs if d not in exclude]

Из справки (os.walk):

Когда topdown имеет значение true, вызывающая сторона может изменить список dirnames на месте (например, посредством назначения del или slice), и walk будет возвращаться только в подкаталоги, чьи имена остаются в dirnames; это может быть использовано для сокращения поиска ...

unutbu
источник
31
Почему dirs[:] =?
бен
56
@ben: dirs[:] = valueизменяет dirs на месте . Он изменяет содержимое списка dirsбез изменения контейнера. Как уже help(os.walk)упоминалось, это необходимо, если вы хотите повлиять на способ os.walkобхода подкаталогов. ( dirs = valueпросто переназначает (или «привязывает») переменную dirsк новому списку, без изменения оригинала dirs.)
unutbu
6
Вы также можете использовать filter():dirs[:] = list(filter(lambda x: not x in exclude, dirs))
NuclearPeon
2
@ p014k: Вы можете написать свою собственную функцию генератора, которая вызывает os.walkи возвращает root, dirs, filesпосле исключения .git(или чего-либо еще) dirs.
Unutbu
3
@unutbu Просто сообщаю, что в одном случае эта оптимизация сократила время обхода с более чем 100 секунд до примерно 2 секунд. Это то, что я называю стоящей оптимизацией. : D
antred
7

... альтернативная форма превосходного ответа @ unutbu, которая читает немного более прямо, учитывая, что цель состоит в том, чтобы исключить каталоги, за счет времени O (n ** 2) против O (n).

(Для list(dirs)правильного выполнения требуется сделать копию списка директорий с помощью )

# exclude = set([...])
for root, dirs, files in os.walk(top, topdown=True):
    [dirs.remove(d) for d in list(dirs) if d in exclude]
Дмитрий
источник
5
Если вы хотите быть более прямым за счет некоторой памяти, вам лучше написать dirs[:] = set(dirs) - exclude. По крайней мере, это все еще \ $ O (n) \ $, и вы не строите понимание только для его побочных эффектов ...
301_Moved_Permanently
3
Это неплохой, но не идиоматичный Python, на мой взгляд.
Торстен Бронджер
for d in list(dirs)немного странно dirsэто уже список. И то, что у вас есть, на самом деле не является списком. dirs.remove(d)ничего не возвращает, так что вы получите полный список Nones. Я согласен с @Torsten.
seanahern