os.walk senza scavare nelle directory sottostanti


103

Come posso limitare os.walka restituire solo i file nella directory che fornisco?

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        for f in files:
            if os.path.splitext(f)[1] in whitelist:
                outputList.append(os.path.join(root, f))
            else:
                self._email_to_("ignore")
    return outputList

2
Un altro caso in cui la moltitudine di possibili approcci e tutte le avvertenze che ne derivano suggerisce che questa funzionalità dovrebbe essere aggiunta alla libreria standard di Python.
antred

files_with_full_path = [f.path for f in os.scandir(dir) if f.is_file()]. Nel caso in cui sia necessario utilizzare solo i nomi dei file f.nameinvece di f.path. Questa è la soluzione più veloce e molto più veloce di qualsiasi altra walko listdir, vedi stackoverflow.com/a/40347279/2441026 .
user136036

Risposte:


105

Usa la walklevelfunzione.

import os

def walklevel(some_dir, level=1):
    some_dir = some_dir.rstrip(os.path.sep)
    assert os.path.isdir(some_dir)
    num_sep = some_dir.count(os.path.sep)
    for root, dirs, files in os.walk(some_dir):
        yield root, dirs, files
        num_sep_this = root.count(os.path.sep)
        if num_sep + level <= num_sep_this:
            del dirs[:]

Funziona esattamente come os.walk, ma puoi passargli un levelparametro che indica quanto in profondità andrà la ricorsione.


3
Questa funzione "percorre" effettivamente l'intera struttura e quindi cancella le voci al di sotto di un certo punto? O sta succedendo qualcosa di più intelligente? Non sono nemmeno sicuro di come verificarlo con il codice. --python principiante
mathtick

1
@mathtick: quando viene trovata una directory al di sopra o al di sotto del livello desiderato, tutte le sue sottodirectory vengono rimosse dall'elenco delle sottodirectory per la ricerca successiva. Quindi non saranno "calpestati".
nosklo

2
Ho appena fatto +1 perché stavo lottando con come "eliminare" le directory. Avevo provato dirs = []e dirs = Nonema quelli non hanno funzionato. map(dirs.remove, dirs)funzionava, ma con alcuni messaggi indesiderati "[Nessuno]" stampati. Quindi, perché nello del dirs[:]specifico?
Zach Young

4
Nota che questo non funziona quando si utilizza topdown=Falsein os.walk. Vedi il 4 ° paragrafo nei documenti :Modifying dirnames when topdown is False has no effect on the behavior of the walk, because in bottom-up mode the directories in dirnames are generated before dirpath itself is generated.
dthor

3
@ZacharyYoung dirs = []e dirs = Nonenon funzionerà perché creano solo un nuovo oggetto non correlato e assegnano il nome dirs. L'oggetto elenco originale deve essere modificato sul posto, non il nome dirs.
nosklo

206

Non utilizzare os.walk.

Esempio:

import os

root = "C:\\"
for item in os.listdir(root):
    if os.path.isfile(os.path.join(root, item)):
        print item

1
@ 576i: questo non fa distinzione tra file e directory

4
@Alexandr os.path.isfilee os.path.isdirti permette di differenziarti. Non lo capisco, poiché os.path.isfileè nel codice di esempio dal '08 e il tuo commento è dal '16. Questa è chiaramente la risposta migliore, poiché non hai intenzione di esplorare una directory, ma di elencarla.
Daniel F

@ DanielF, quello che volevo dire qui è che devi eseguire il ciclo su tutti gli elementi, mentre walkti dà immediatamente gli elenchi separati di directory e file.

Ah ok. In realtà la risposta di Alex sembra essere migliore (usando .next()) ed è molto più vicina alla tua idea.
Daniel F

Python 3.5 ha una os.scandirfunzione che consente un'interazione file-o-directory-oggetto più sofisticata. Vedi la mia risposta di seguito
ascripter

48

Penso che la soluzione sia in realtà molto semplice.

uso

break

per fare solo la prima iterazione del ciclo for, ci deve essere un modo più elegante.

for root, dirs, files in os.walk(dir_name):
    for f in files:
        ...
        ...
    break
...

La prima volta che chiami os.walk, restituisce tulipani per la directory corrente, quindi al ciclo successivo il contenuto della directory successiva.

Prendi il copione originale e aggiungi solo una pausa .

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        for f in files:
            if os.path.splitext(f)[1] in whitelist:
                outputList.append(os.path.join(root, f))
            else:
                self._email_to_("ignore")
        break
    return outputList

9
Questa avrebbe dovuto essere la risposta accettata. La semplice aggiunta di una "interruzione" dopo il ciclo "for f in files" interrompe la ricorsività. Potresti anche assicurarti che topdown = True.
Alecz

23

Il suggerimento da utilizzare listdirè buono. La risposta diretta alla tua domanda in Python 2 è root, dirs, files = os.walk(dir_name).next().

La sintassi equivalente di Python 3 è root, dirs, files = next(os.walk(dir_name))


1
Oh stavo ricevendo ogni sorta di errore divertente da quello. ValueError: troppi valori da decomprimere
Setori

1
Bello! Sembra un hack, però. Come quando si accende un motore ma gli si lascia fare solo un giro e poi si tira la chiave per farlo morire.
Daniel F

Ci siamo imbattuti in questo; root, dirs, files = os.walk(dir_name).next()mi dàAttributeError: 'generator' object has no attribute 'next'
Evan

3
@Evan, probabilmente perché è del 2008 e utilizza la sintassi di Python 2. In Python 3 puoi scrivere root, dirs, files = next(os.walk(dir_name))e quindi le variabili root, dirs, filescorrisponderanno solo alle variabili del generatore a dir_namelivello.
CervEd

13

È possibile utilizzare os.listdir()which restituisce un elenco di nomi (sia per i file che per le directory) in una determinata directory. Se hai bisogno di distinguere tra file e directory, chiama os.stat()ogni nome.


9

Se hai requisiti più complessi rispetto alla sola directory principale (ad esempio, ignora le directory VCS, ecc.), Puoi anche modificare l'elenco delle directory per evitare che os.walk ricorra attraverso di esse.

vale a dire:

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        dirs[:] = [d for d in dirs if is_good(d)]
        for f in files:
            do_stuff()

Nota: fai attenzione a modificare l'elenco, piuttosto che ricollegarlo. Ovviamente os.walk non conosce il rebinding esterno.


6
for path, dirs, files in os.walk('.'):
    print path, dirs, files
    del dirs[:] # go only one level deep

4

La stessa idea con listdir, ma più breve:

[f for f in os.listdir(root_dir) if os.path.isfile(os.path.join(root_dir, f))]

3

Mi è sembrato di buttare i miei 2 pence.

baselevel = len(rootdir.split("\\"))
for subdirs, dirs, files in os.walk(rootdir):
    curlevel = len(subdirs.split("\\"))
    if curlevel <= baselevel + 1:
        [do stuff]

2

In Python 3, sono stato in grado di farlo:

import os
dir = "/path/to/files/"

#List all files immediately under this folder:
print ( next( os.walk(dir) )[2] )

#List all folders immediately under this folder:
print ( next( os.walk(dir) )[1] )

Funziona anche per Python 2. Come si arriva al secondo livello?

2

A partire da Python 3.5 puoi usare os.scandirinvece di os.listdir. Invece di stringhe ottieni DirEntryin cambio un iteratore di oggetti. Dai documenti:

L'utilizzo di scandir()invece di listdir()può aumentare in modo significativo le prestazioni del codice che richiede anche informazioni sul tipo di file o sugli attributi del file, poiché gli DirEntryoggetti espongono queste informazioni se il sistema operativo le fornisce durante la scansione di una directory. Tutti i DirEntrymetodi possono effettuare una chiamata di sistema, ma is_dir()e is_file()di solito richiedono solo una chiamata di sistema per i collegamenti simbolici; DirEntry.stat()richiede sempre una chiamata di sistema su Unix ma ne richiede solo una per i collegamenti simbolici su Windows.

È possibile accedere al nome dell'oggetto tramite il DirEntry.namequale è quindi equivalente all'output dios.listdir


1
Non solo "puoi" usare, dovresti usare scandir(), poiché è molto più veloce di listdir(). Vedi i benchmark qui: stackoverflow.com/a/40347279/2441026 .
user136036

1

Puoi anche fare quanto segue:

for path, subdirs, files in os.walk(dir_name):
    for name in files:
        if path == ".": #this will filter the files in the current directory
             #code here

2
Questo ciclo attraverso tutte le sottodirectory e i file non sarà inutilmente?
Pieter

0

Ecco come l'ho risolto

if recursive:
    items = os.walk(target_directory)
else:
    items = [next(os.walk(target_directory))]

...

0

C'è un problema quando si usa listdir. Os.path.isdir (identificatore) deve essere un percorso assoluto. Per scegliere le sottodirectory fai:

for dirname in os.listdir(rootdir):
  if os.path.isdir(os.path.join(rootdir, dirname)):
     print("I got a subdirectory: %s" % dirname)

L'alternativa è passare alla directory per eseguire il test senza os.path.join ().


0

Puoi usare questo snippet

for root, dirs, files in os.walk(directory):
    if level > 0:
        # do some stuff
    else:
        break
    level-=1

0

creare un elenco di esclusioni, utilizzare fnmatch per saltare la struttura della directory ed eseguire il processo

excludes= ['a\*\b', 'c\d\e']
for root, directories, files in os.walk('Start_Folder'):
    if not any(fnmatch.fnmatch(nf_root, pattern) for pattern in excludes):
        for root, directories, files in os.walk(nf_root):
            ....
            do the process
            ....

come per "include":

if **any**(fnmatch.fnmatch(nf_root, pattern) for pattern in **includes**):

0

Perché non usare semplicemente un rangee os.walkcombinato con il zip? Non è la soluzione migliore, ma funzionerebbe anche.

Ad esempio in questo modo:

# your part before
for count, (root, dirs, files) in zip(range(0, 1), os.walk(dir_name)):
    # logic stuff
# your later part

Funziona per me su Python 3.

Inoltre: A breakè anche più semplice. (Guarda la risposta di @Pieter)


0

Una leggera modifica alla risposta di Alex, ma utilizzando __next__():

print(next(os.walk('d:/'))[2]) o print(os.walk('d:/').__next__()[2])

con l' [2]essere il filein root, dirs, filedetto in altre risposte


0

modifiche alla cartella principale per ogni directory trovata da os.walk. Lo risolvo controllando se root == directory

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        if root == dir_name: #This only meet parent folder
            for f in files:
                if os.path.splitext(f)[1] in whitelist:
                    outputList.append(os.path.join(root, f))
                else:
                    self._email_to_("ignore")
    return outputList

0
import os

def listFiles(self, dir_name):
    names = []
    for root, directory, files in os.walk(dir_name):
        if root == dir_name:
            for name in files:
                names.append(name)
    return names

1
Ciao Rich, benvenuto in Stack Overflow! Grazie per questo snippet di codice, che potrebbe fornire un aiuto limitato a breve termine. Una spiegazione adeguata migliorerebbe notevolmente il suo valore a lungo termine mostrando perché questa è una buona soluzione al problema e la renderebbe più utile per i futuri lettori con altre domande simili. Si prega di modificare la risposta di aggiungere qualche spiegazione, tra le ipotesi che hai fatto.
kenny_k
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.