2011-07-20 3 views
50

J'essaye de faire quelque chose à tous les fichiers sous un chemin donné. Je ne veux pas recueillir tous les noms de fichiers au préalable puis faire quelque chose avec eux, donc j'ai essayé ceci:Rendement dans une fonction récursive

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 

Mais ce code saute répertoires quand il les frappe, au lieu de céder leur contenu. Qu'est-ce que je fais mal?

+0

Certaines langues peuvent générer une séquence entière, pas seulement des éléments individuels. Je ne pense pas que Python soit l'un d'entre eux. http://www.mindscapehq.com/blog/index.php/2011/02/28/recursive-iterators-in-f/ – Leonid

+0

Puisque le titre suggère un problème plus général que os.walk peut résoudre, considérez ceci : Explorons def (p): si isinstance (p, (liste, tuple)): pour x dans p: Explorons (p) autre: rendement p Cela a le même problème. Pourquoi ça ne marche pas? – JimB

Répondre

26

Utilisation os.walk au lieu de réinventer la roue.

En particulier, suivant les exemples dans la documentation de la bibliothèque, voici une tentative non testé:

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

Donner du code de travail est bon, mais expliquer ce que le PO a fait de mal, surtout quand ils le demandent, c'est encore mieux. –

+1

la question est sur le rendement et la récursivité et pas sur la meilleure façon de mettre en œuvre le os.walk – Massimo

+0

aussi: en Python 2 marcher est plus lent, puis listdir, voir https://www.python.org/dev/peps/pep-0471/ – Massimo

2

Essayez ceci:

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

qui appelle explore comme une fonction. Ce que vous devez faire est itérer comme un générateur:

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

EDIT: Au lieu du module stat, vous pouvez utiliser os.path.isdir(path).

123

itérateurs ne fonctionnent pas récursive comme ça. Vous devez re-donner chaque résultat, en remplaçant

explore(path) 

avec quelque chose comme

for value in explore(path): 
    yield value 

Python 3.3 ajouté la syntaxe yield from X, tel que proposé dans PEP 380, pour servir cet objectif. Avec elle, vous pouvez le faire à la place:

yield from explore(path) 

Si vous utilisez generators as coroutines, cette syntaxe prend également en charge l'utilisation de generator.send() pour transmettre des valeurs dans les générateurs récursive-invoqués. La simple boucle for ci-dessus ne le ferait pas.

+13

+1 pour mentionner une fonctionnalité de 3,3 que je n'étais pas au courant précédemment :) – phooji

+17

Cela devrait être la réponse acceptée à mon humble avis, comme la question est sur le rendement et la récursivité et non sur la meilleure façon de mettre en œuvre le os.walk ;-) !! ! Je me suis cassé la tête sur cette boucle très simple ... Et en fait toutes les autres réponses sont sur la même ligne ... – Stefano

+2

+1 pour le lien vers le PEP qui explique tout cela de manière beaucoup plus détaillée. – mpontillo

8

Modifier ceci:

explore(path) 

à ceci:

for subpath in explore(path): 
    yield subpath 

Ou utiliser os.walk, comme suggéré phooji (ce qui est la meilleure option).

35

Le problème est cette ligne de code:

explore(path) 

Que faut-il faire?

  • appelle explore avec les nouveaux path
  • explore fonctionne, ce qui crée un générateur
  • le générateur est retourné à l'endroit où explore(path) a été exécuté . . .
  • et est mis au rebut

Pourquoi est-il jeté? Il n'était pas assigné à quoi que ce soit, il n'était pas réitéré - il était complètement ignoré.

Si vous voulez faire quelque chose avec les résultats, eh bien, vous devez faire quelque chose avec eux! ;)

La meilleure façon de corriger votre code est:

for name in explore(path): 
    yield name 

Lorsque vous êtes confiant que vous comprenez ce qui se passe, vous voudrez probablement utiliser os.walk() à la place.

Une fois que vous avez migré vers Python 3.3 (en supposant que tout fonctionne comme prévu), vous serez en mesure d'utiliser la nouvelle syntaxe yield from et la meilleure façon de corriger votre code à ce moment-là sera:

yield from explore(path) 
+6

+1 bonne explication de la raison pour laquelle le code de l'affiche originale n'a pas fonctionné. –

0

os.walk est génial si vous avez besoin de parcourir tous les dossiers et sous-dossiers. Si vous n'en avez pas besoin, c'est comme utiliser un pistolet à éléphant pour tuer une mouche. Cependant, dans ce cas précis, os.walk pourrait être une meilleure approche.

0

Vous pouvez également implémenter la récursivité en utilisant une pile.

Il n'y a pas vraiment d'avantage à faire cela, sauf que c'est possible. Si vous utilisez python en premier lieu, les gains de performance ne valent probablement pas la peine.

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('.')) 
Questions connexes