53 votes

Rendement dans une fonction récursive

Je essaie de faire quelque chose à tous les fichiers sous un chemin donné. Je ne veux pas collecter tous les noms de fichiers au préalable puis faire quelque chose avec eux, donc j'ai essayé ceci :

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 les répertoires lorsqu'il les atteint, au lieu de donner leur contenu. Qu'est-ce que je fais de faux ?

0 votes

Certaines langues peuvent produire une séquence entière, pas simplement des éléments individuels. Je ne pense pas que Python en fasse partie. mindscapehq.com/blog/index.php/2011/02/28/…

0 votes

Depuis le titre suggère un problème plus général que celui pouvant être résolu par os.walk, considérons ceci: def explorer(p): if isinstance(p, (list, tuple)): for x in p: explorer(p) else: yield p Cela a le même problème. Pourquoi cela ne fonctionne-t-il pas?

128voto

Jeremy Banks Points 32470

Les itérateurs ne fonctionnent pas de manière récursive comme ça. Vous devez réinitialiser chaque résultat en remplaçant

explore(chemin)

par quelque chose comme

for valeur in explore(chemin):
    yield valeur

Python 3.3 a ajouté la syntaxe yield from X, comme proposé dans PEP 380, pour servir à cette fin. Avec cela, vous pouvez faire ceci à la place :

yield from explore(chemin)

Si vous utilisez des générateurs comme des coroutines, cette syntaxe prend également en charge l'utilisation de generator.send() pour renvoyer des valeurs aux générateurs invoqués de manière récursive. La simple boucle for ci-dessus ne le ferait pas.

17 votes

Cela devrait être la réponse acceptée à mon avis, car la question porte sur yield et la récursion et non sur la meilleure façon de mettre en œuvre os.walk ;-) !!! Je me creusais la tête sur cette simple boucle... Et en fait, toutes les autres réponses vont dans le même sens...

0 votes

Merci mec! La mention de 3.3 et des itérateurs spécifiques était vraiment utile.

0 votes

Comme mentionné dans cette discussion à PyCon2014, les générateurs peuvent être utilisés pour contourner la limite de récursion! youtube.com/watch?v=D1twn9kLmYg

37voto

Ethan Furman Points 12683

Le problème est cette ligne de code :

explore(chemin)

Que fait-il ?

  • appelle explore avec le nouveau chemin
  • explore s'exécute, créant un générateur
  • le générateur est renvoyé à l'endroit où explore(chemin) a été exécuté . . .
  • et est jeté

Pourquoi est-il jeté ? Il n'a été affecté à rien, il n'a pas été itéré - il a été complètement ignoré.

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

La manière la plus simple de corriger votre code est :

for nom in explore(chemin):
    yield nom

Lorsque vous êtes sûr de comprendre ce qui se passe, vous voudrez probablement utiliser os.walk() à la place.

Une fois que vous aurez migré vers Python 3.3 (en supposant que tout se passe comme prévu), vous pourrez utiliser la nouvelle syntaxe yield from et la manière la plus simple de corriger votre code à ce moment-là sera :

yield from explore(chemin)

26voto

phooji Points 5692

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

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

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)

# appeler et obtenir la liste complète des résultats :
allfiles = [ x for x in hellothere("...") ]

# itérer paresseusement sur les résultats :
for x in hellothere("..."):
    print x

15 votes

Donner un code fonctionnel est bon, mais expliquer ce que le demandeur a mal fait, surtout lorsqu'ils le demandent, c'est encore mieux.

3 votes

La question concerne le rendement et la récursivité et non la meilleure façon de mettre en œuvre la fonction os.walk.

0 votes

Aussi : en Python 2, walk est plus lent que listdir, voir python.org/dev/peps/pep-0471

8voto

Dietrich Epp Points 72865

Change this:

explore(path)

To this:

for subpath in explore(path):
    yield subpath

Or use os.walk, as phooji suggested (which is the better option).

2voto

satoru Points 6608

Essayez ceci :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X