J'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, alors 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 __== "__main__":
for x in explore('.'):
print '-->', x
Mais ce code saute les répertoires quand il les frappe, au lieu de donner leur contenu. Qu'est-ce que je fais mal?
Utilisez os.walk
au lieu de réinventer la roue.
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)
# call and get full list of results:
allfiles = [ x for x in hellothere("...") ]
# iterate over results lazily:
for x in hellothere("..."):
print x
Les itérateurs ne fonctionnent pas de manière récursive comme ça. Vous devez renvoyer chaque résultat en remplaçant
explore(path)
avec quelque chose comme
for value in explore(path):
yield value
Python 3.3 a ajouté la syntaxe yield from X
, comme proposé dans PEP 380 , afin de répondre à cet objectif. Avec cela, vous pouvez le faire à la place:
yield from explore(path)
Si vous utilisez generators en tant que coroutines , cette syntaxe prend également en charge l'utilisation de generator.send()
pour renvoyer des valeurs dans les générateurs invoqués de manière récursive. La simple boucle for
ci-dessus ne le ferait pas.
Le problème est cette ligne de code:
explore(path)
Qu'est ce que ça fait?
explore
avec la nouvelle path
explore
s'exécute, créant un générateurexplore(path)
a été exécuté. . . 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! ;)
Le moyen le plus simple de corriger votre code est:
for name in explore(path):
yield name
Lorsque vous êtes certain de comprendre 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 pourrez utiliser la nouvelle syntaxe yield from
et le moyen le plus simple de corriger votre code à ce stade sera le suivant:
yield from explore(path)
Change ça:
explore(path)
Pour ça:
for subpath in explore(path):
yield subpath
Ou utilisez os.walk
, comme suggéré par phooji (qui est la meilleure option).
Cela appelle explore
comme une fonction. Ce que vous devriez faire, c'est l'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)
.
Essaye ça:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
Vous pouvez également implémenter la récursivité à l'aide d'une pile.
Cela ne présente toutefois aucun avantage, mis à part le fait que cela est possible. Si vous utilisez python en premier lieu, les gains de performances ne sont probablement pas intéressants.
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('.'))
os.walk est idéal si vous devez parcourir tous les dossiers et sous-dossiers. Si vous n'en avez pas besoin, c'est comme utiliser une arme à éléphant pour tuer une mouche.
Cependant, dans ce cas particulier, os.walk pourrait être une meilleure approche.
Pour répondre à la question d'origine telle qu'elle a été posée, la clé est que l'instruction yield
doit être propagée en dehors de la récursion (comme par exemple return
). Voici une réimplémentation de travail de os.walk()
. J'utilise ceci dans une implémentation de pseudo-VFS, où je remplace en plus os.listdir()
et des appels similaires.
import os, os.path
def walk (top, topdown=False):
items = ([], [])
for name in os.listdir(top):
isdir = os.path.isdir(os.path.join(top, name))
items[isdir].append(name)
result = (top, items[True], items[False])
if topdown:
yield result
for folder in items[True]:
for item in walk(os.path.join(top, folder), topdown=topdown):
yield item
if not topdown:
yield result