Exclusion de répertoires dans os.walk

148

J'écris un script qui descend dans une arborescence de répertoires (en utilisant os.walk ()), puis visite chaque fichier correspondant à une certaine extension de fichier. Cependant, comme certaines des arborescences de répertoires sur lesquelles mon outil sera utilisé contiennent également des sous-répertoires qui à leur tour contiennent BEAUCOUP de choses inutiles (aux fins de ce script), j'ai pensé que j'ajouterais une option pour que l'utilisateur spécifie une liste de répertoires à exclure de la traversée.

C'est assez simple avec os.walk (). Après tout, c'est à moi de décider si je veux réellement visiter les fichiers / répertoires respectifs générés par os.walk () ou simplement les ignorer. Le problème est que si j'ai, par exemple, une arborescence de répertoires comme celle-ci:

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

et je veux exclure uselessStuff et tous ses enfants, os.walk () descendra toujours dans tous les sous-répertoires (potentiellement des milliers de) de uselessStuff , ce qui, inutile de le dire, ralentit beaucoup les choses. Dans un monde idéal, je pourrais dire à os.walk () de ne même pas se donner la peine de donner plus d'enfants de inutileStuff , mais à ma connaissance, il n'y a aucun moyen de le faire (n'est-ce pas?).

est-ce que quelqu'un a une idée? Peut-être existe-t-il une bibliothèque tierce qui fournit quelque chose comme ça?

antred
la source

Réponses:

243

La modification dirs sur place élagera les fichiers et répertoires (suivants) visités par 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]

Depuis l'aide (os.walk):

Lorsque topdown est vrai, l'appelant peut modifier la liste des noms de répertoires sur place (par exemple, via une attribution de suppression ou de tranche), et marcher ne rentrera que dans les sous-répertoires dont les noms restent dans les noms de répertoire; cela peut être utilisé pour élaguer la recherche ...

unutbu
la source
31
Pourquoi dirs[:] =?
ben
56
@ben: dirs[:] = valuemodifie dirs sur place . Il modifie le contenu de la liste dirssans changer le conteneur. Comme help(os.walk)mentionné, cela est nécessaire si vous souhaitez affecter la façon dont os.walktraverse les sous-répertoires. ( dirs = valueréaffecte simplement (ou "lie") la variable dirsà une nouvelle liste, sans modifier l'original dirs.)
unutbu
6
Vous pouvez également utiliser filter():dirs[:] = list(filter(lambda x: not x in exclude, dirs))
NuclearPeon
2
@ p014k: Vous pouvez écrire votre propre fonction de générateur qui appelle os.walket produit root, dirs, filesaprès avoir exclu .git(ou tout ce que vous souhaitez) dirs.
unutbu
3
@unutbu Je vous informe simplement que dans un cas, cette optimisation a réduit le temps de parcours de plus de 100 secondes à environ 2 secondes. C'est ce que j'appelle une optimisation valable. : D
antred le
7

... une forme alternative de l'excellente réponse de @ unutbu qui se lit un peu plus directement, étant donné que l'intention est d' exclure les répertoires, au prix du temps O (n ** 2) vs O (n).

(Faire une copie de la liste des répertoires avec list(dirs)est nécessaire pour une exécution correcte)

# exclude = set([...])
for root, dirs, files in os.walk(top, topdown=True):
    [dirs.remove(d) for d in list(dirs) if d in exclude]
Dmitri
la source
5
Si vous voulez être plus direct au prix d'un peu de mémoire, vous feriez mieux d'écrire dirs[:] = set(dirs) - exclude. Au moins, c'est toujours \ $ O (n) \ $ et vous ne construisez pas une compréhension uniquement pour ses effets secondaires ...
301_Moved_Permanently
3
Ce n'est pas vraiment mauvais mais pas idiomatique Python non plus à mon avis.
Torsten Bronger
for d in list(dirs)c'est un peu bizarre. dirsest déjà une liste. Et ce que vous avez n'est pas vraiment une compréhension de liste. dirs.remove(d)ne renvoie rien, donc vous vous retrouvez avec une liste pleine de Nones. Je suis d'accord avec @Torsten.
seanahern le