Escluse directory in os.walk


148

Sto scrivendo uno script che scende in un albero di directory (usando os.walk ()) e quindi visita ogni file corrispondente a una determinata estensione. Tuttavia, poiché alcuni degli alberi di directory su cui verrà utilizzato il mio strumento contengono anche sottodirectory che a loro volta contengono MOLTE cose inutili (ai fini di questo script), ho pensato che avrei aggiunto un'opzione per l'utente di specificare un elenco di directory da escludere dall'attraversamento.

Questo è abbastanza facile con os.walk (). Dopotutto, spetta a me decidere se voglio effettivamente visitare i rispettivi file / dir prodotti da os.walk () o semplicemente saltarli. Il problema è che se ho, ad esempio, un albero di directory come questo:

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

e voglio escludere inutilmente Stuff e tutti i suoi figli, os.walk () scenderà comunque in tutte le (potenzialmente migliaia di) sottodirectory di inutile Stuff , che, inutile dirlo, rallenta molto le cose. In un mondo ideale, potrei dire a os.walk () di non preoccuparsi nemmeno di cedere altri figli di oggetti inutili , ma per quanto ne sappia non c'è modo di farlo (vero?).

Qualcuno ha un'idea? Forse c'è una biblioteca di terze parti che fornisce qualcosa del genere?

Risposte:


243

La modifica dirs sul posto eliminerà i file (successivi) e le directory visitati da 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]

Da help (os.walk):

Quando il topdown è vero, il chiamante può modificare sul posto l'elenco dei dirnames (ad es. Tramite del o assegnazione di slice), e walk ricorre solo nelle sottodirectory i cui nomi rimangono nei dirnames; questo può essere usato per potare la ricerca ...


31
Perché dirs[:] =?
ben

56
@ben: dirs[:] = valuemodifica dirs sul posto . Cambia il contenuto dell'elenco dirssenza cambiare il contenitore. Come help(os.walk)menzionato, questo è necessario se si desidera influenzare il modo in cui os.walkattraversa le sottodirectory. ( dirs = valueriassegna semplicemente (o "lega") la variabile dirsa un nuovo elenco, senza modificare l'originale dirs.)
unutbu,

6
Puoi anche usare filter():dirs[:] = list(filter(lambda x: not x in exclude, dirs))
NuclearPeon

2
@ p014k: potresti scrivere la tua funzione di generatore che chiama os.walke cede root, dirs, filesdopo aver escluso .git(o qualunque altra cosa tu voglia) dirs.
unutbu,

3
@unutbu Solo per farti sapere che in un caso, questa ottimizzazione ha ridotto il tempo di attraversamento da più di 100 secondi a circa 2 secondi. Questo è ciò che chiamo un'ottimizzazione utile. : D
antred

7

... una forma alternativa dell'eccellente risposta di @ unutbu che legge un po 'più direttamente, dato che l'intento è quello di escludere le directory, a costo di O (n ** 2) vs O (n) tempo.

(Per una list(dirs)corretta esecuzione è necessaria una copia dell'elenco dirs con

# 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
Se vuoi essere più diretto al costo di un po 'di memoria, è meglio scrivere dirs[:] = set(dirs) - exclude. Almeno è ancora \ $ O (n) \ $ e non costruisci una comprensione solo per i suoi effetti collaterali ...
301_Moved_Permanently

3
Questo non è affatto male, ma nemmeno idiomatico Python secondo me.
Torsten Bronger,

for d in list(dirs)è un po 'strano. dirsè già un elenco. E quello che hai non è davvero una comprensione dell'elenco. dirs.remove(d)non restituisce nulla, quindi si finisce con un elenco pieno di Nones. Sono d'accordo con @Torsten.
Seanahern
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.