2011-07-20 20 views
50

Sto provando a fare qualcosa su tutti i file sotto un determinato percorso. Non voglio per raccogliere tutti i nomi dei file in anticipo poi fare qualcosa con loro, così ho provato questo:Resa in una funzione ricorsiva

import os 
import stat 

def explore(p): 
    s = '' 
    list = os.listdir(p) 
    for a in list: 
    path = p + '/' + a 
    stat_info = os.lstat(path) 
    if stat.S_ISDIR(stat_info.st_mode): 
    explore(path) 
    else: 
     yield path 

if __name__ == "__main__": 
    for x in explore('.'): 
    print '-->', x 

Ma questo codice salta sopra le directory quando li colpisce, invece di cedere il loro contenuto. Che cosa sto facendo di sbagliato?

+0

Alcune lingue possono produrre un'intera sequenza, non solo singoli elementi. Non penso che Python sia uno di questi. http://www.mindscapehq.com/blog/index.php/2011/02/28/recursive-iterators-in-f/ – Leonid

+0

Poiché il titolo suggerisce un problema più generale di quello che può essere risolto da os.walk, considera questo : def esplorare (p): se isinstance (p, (la lista, tuple)): for x in p: esplorare (p) altro: resa p Questo ha lo stesso problema. Perché non funziona? – JimB

risposta

26

Utilizzare os.walk invece di reinventare la ruota.

In particolare, seguendo gli esempi nella documentazione biblioteca, qui è un tentativo testata:

import os 
from os.path import join 

def hellothere(somepath): 
    for root, dirs, files in os.walk(somepath): 
     for curfile in files: 
      yield join(root, curfile) 


# call and get full list of results: 
allfiles = [ x for x in hellothere("...") ] 

# iterate over results lazily: 
for x in hellothere("..."): 
    print x 
+13

Dare un codice di lavoro è buono, ma spiegare che cosa ha sbagliato l'OP, specialmente quando lo chiedono, è ancora meglio. –

+1

la domanda riguarda la resa e la ricorsione e non il modo migliore per implementare l'os.walk – Massimo

+0

anche: in Python 2 è più lento quindi listdir, vedere https://www.python.org/dev/peps/pep-0471/ – Massimo

2

Prova questa:

if stat.S_ISDIR(stat_info.st_mode): 
    for p in explore(path): 
     yield p 
3

che chiama explore come una funzione. Che cosa si dovrebbe fare è un'iterazione come un generatore:

if stat.S_ISDIR(stat_info.st_mode): 
    for p in explore(path): 
    yield p 
else: 
    yield path 

EDIT: al posto del modulo stat, è possibile utilizzare os.path.isdir(path).

123

Gli iteratori non funzionano in modo ricorsivo in questo modo. È necessario ri-produrre ogni risultato, sostituendo

explore(path) 

con qualcosa di simile

for value in explore(path): 
    yield value 

Python 3.3 aggiunto la sintassi yield from X, come proposto in PEP 380, per servire a questo scopo. Con essa è possibile fare questo, invece:

yield from explore(path) 

Se stai usando generators as coroutines, questa sintassi supporta anche l'uso di generator.send() per passare valori nuovamente dentro i generatori ricorsivamente-invocati. Il semplice ciclo for sopra non lo farebbe.

+13

+1 per aver menzionato una funzione 3.3 di cui non ero a conoscenza in precedenza :) – phooji

+17

Questa dovrebbe essere la risposta accettata IMHO, poiché la domanda riguarda la resa e la ricorsione e non il modo migliore per implementare l'os.walk ;-) !! ! Mi stavo rompendo la testa in questo ciclo molto semplice ... E in realtà tutte le altre risposte sono sulla stessa linea ... – Stefano

+2

+1 per il link al PEP che spiega tutto questo in modo molto più dettagliato. – mpontillo

8

Modifica questo:

explore(path) 

A tal:

for subpath in explore(path): 
    yield subpath 

Oppure usano os.walk, come suggerito phooji (che è l'opzione migliore).

35

Il problema è questa riga di codice:

explore(path) 

Che cosa fa?

  • chiama explore con le nuove path
  • explore piste, creando un generatore
  • il generatore viene restituito al punto in cui è stato eseguito explore(path). . .
  • e viene scartato

Perché è scartato? Non è stato assegnato a nulla, non è stato ripetuto, è stato completamente ignorato.

Se vuoi fare qualcosa con i risultati, beh, devi fare qualcosa con loro! ;)

Il modo più semplice per risolvere il tuo codice è:

for name in explore(path): 
    yield name 

Quando siete sicuri di capire cosa sta succedendo, probabilmente si vorrà utilizzare os.walk() invece.

Una volta che la migrazione a Python 3.3 (supponendo che tutto funziona come previsto) si sarà in grado di utilizzare la nuova yield from sintassi e il modo più semplice per risolvere il codice a quel punto sarà:

yield from explore(path) 
+6

+1 buona spiegazione del motivo per cui il codice del poster originale non ha funzionato. –

0

os.walk è fantastico se devi attraversare tutte le cartelle e le sottocartelle. Se non ne hai bisogno, è come usare una pistola ad elefante per uccidere una mosca.

Tuttavia, per questo caso specifico, os.walk potrebbe essere un approccio migliore.

0

È inoltre possibile implementare la ricorsione utilizzando una pila.

Tuttavia, non c'è alcun vantaggio nel farlo, a parte il fatto che è possibile. Se si sta utilizzando Python, in primo luogo, i guadagni in termini di prestazioni probabilmente non ne valgono la pena.

import os 
import stat 

def explore(p): 
    ''' 
    perform a depth first search and yield the path elements in dfs order 
     -implement the recursion using a stack because a python can't yield within a nested function call 
    ''' 
    list_t=type(list()) 
    st=[[p,0]] 
    while len(st)>0: 
     x=st[-1][0] 
     print x 
     i=st[-1][1] 

     if type(x)==list_t: 
      if i>=len(x): 
       st.pop(-1) 
      else: 
       st[-1][1]+=1 
       st.append([x[i],0]) 
     else: 
      st.pop(-1) 
      stat_info = os.lstat(x) 
      if stat.S_ISDIR(stat_info.st_mode): 
       st.append([['%s/%s'%(x,a) for a in os.listdir(x)],0]) 
      else: 
       yield x 

print list(explore('.')) 
Problemi correlati