Excluyendo directorios en os.walk


148

Estoy escribiendo un script que desciende a un árbol de directorios (usando os.walk ()) y luego visita cada archivo que coincide con una determinada extensión de archivo. Sin embargo, dado que algunos de los árboles de directorios en los que se usará mi herramienta también contienen subdirectorios que a su vez contienen MUCHAS cosas inútiles (para el propósito de este script), pensé que agregaría una opción para que el usuario especifique una lista de directorios para excluir del recorrido.

Esto es bastante fácil con os.walk (). Después de todo, depende de mí decidir si realmente quiero visitar los archivos / directorios respectivos producidos por os.walk () o simplemente omitirlos. El problema es que si tengo, por ejemplo, un árbol de directorios como este:

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

y quiero excluir a uselessStuff y todos sus elementos secundarios, os.walk () seguirá descendiendo a todos los (potencialmente miles de) subdirectorios de uselessStuff , lo que, no hace falta decirlo, ralentiza mucho las cosas. En un mundo ideal, podría decirle a os.walk () que ni siquiera se moleste en dar más hijos de inútil Stuff , pero que yo sepa, no hay forma de hacerlo (¿existe?).

¿Alguien tiene alguna idea? ¿Tal vez hay una biblioteca de terceros que proporciona algo así?

Respuestas:


243

La modificación dirs in situ eliminará los archivos y directorios (posteriores) visitados por 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]

De ayuda (os.walk):

Cuando topdown es verdadero, la persona que llama puede modificar la lista de nombres de directorio en el lugar (por ejemplo, a través de la asignación del o slice), y walk solo se repetirá en los subdirectorios cuyos nombres permanecen en los nombres de directorio; esto se puede usar para podar la búsqueda ...


31
¿Por qué dirs[:] =?
ben

56
@ben: dirs[:] = valuemodifica dirs en el lugar . Cambia el contenido de la lista dirssin cambiar el contenedor. Como se help(os.walk)menciona, esto es necesario si desea afectar la forma en que os.walkatraviesa los subdirectorios. ( dirs = valuesimplemente reasigna (o "vincula") la variable dirsa una nueva lista, sin modificar el original dirs.)
unutbu

66
También puede usar filter():dirs[:] = list(filter(lambda x: not x in exclude, dirs))
NuclearPeon

2
@ p014k: podría escribir su propia función de generador que llame os.walky rinda root, dirs, filesdespués de excluir .git(o cualquier otra cosa que desee) dirs.
unutbu

3
@unutbu Solo para hacerle saber que en un caso, esta optimización ha reducido el tiempo de recorrido de más de 100 segundos a aproximadamente 2 segundos. Eso es lo que yo llamo una optimización que vale la pena. : D
antred

7

... una forma alternativa de la excelente respuesta de @unutbu que se lee un poco más directamente, dado que la intención es excluir directorios, a costa de O (n ** 2) vs O (n) tiempo.

( list(dirs)Se requiere hacer una copia de la lista de directorios con la ejecución correcta)

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

55
Si quieres ser más directo a costa de algo de memoria, será mejor que escribas dirs[:] = set(dirs) - exclude. Al menos sigue siendo \ $ O (n) \ $ y no construyes una comprensión solo por sus efectos secundarios ...
301_Moved_Permanently

3
Esto no es realmente malo, pero tampoco Python idiomático en mi opinión.
Torsten Bronger

for d in list(dirs)es un poco extraño dirsYa es una lista. Y lo que tienes no es realmente una lista de comprensión. dirs.remove(d)no devuelve nada, así que terminas con una lista llena de Nones. Estoy de acuerdo con @Torsten.
seanahern
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.