Ich versuche, alle Dateien unter einem bestimmten Pfad zu ändern. Ich möchte nicht alle Dateinamen vorher sammeln und dann etwas mit ihnen machen, also habe ich folgendes versucht:
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
Dieser Code überspringt jedoch Verzeichnisse, wenn er auf sie trifft, anstatt deren Inhalt aufzugeben. Was mache ich falsch?
Verwenden Sie os.walk
, anstatt das Rad neu zu erfinden.
Nach den Beispielen in der Bibliotheksdokumentation handelt es sich hier insbesondere um einen nicht getesteten Versuch:
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
Iteratoren arbeiten so nicht rekursiv. Sie müssen jedes Ergebnis erneut erhalten, indem Sie es ersetzen
explore(path)
mit so etwas
for value in explore(path):
yield value
Python 3.3 fügt die in PEP 380 vorgeschlagene Syntax yield from X
hinzu, um diesem Zweck zu dienen. Damit können Sie stattdessen Folgendes tun:
yield from explore(path)
Wenn Sie Generatoren als Coroutinen verwenden, unterstützt diese Syntax auch die Verwendung von generator.send()
, um Werte zurück an die rekursiv aufgerufenen Generatoren zu übergeben. Die einfache for
-Schleife oben würde dies nicht tun.
Das Problem ist diese Codezeile:
explore(path)
Was tut es?
explore
mit der neuen path
aufexplore
läuft und erstellt einen Generatorexplore(path)
ausgeführt wurde. . . Warum wird es verworfen? Es wurde nichts zugewiesen, es wurde nicht wiederholt - es wurde vollständig ignoriert.
Wenn Sie etwas mit den Ergebnissen machen wollen, dann müssen Sie etwas mit ihnen tun! ;)
Der einfachste Weg, Ihren Code zu korrigieren, ist:
for name in explore(path):
yield name
Wenn Sie sicher sind, dass Sie wissen, was los ist, möchten Sie wahrscheinlich stattdessen os.walk()
verwenden.
Nach der Migration zu Python 3.3 (vorausgesetzt, alles läuft wie geplant), können Sie die neue yield from
-Syntax verwenden. Die einfachste Möglichkeit, Ihren Code an diesem Punkt zu korrigieren, ist:
yield from explore(path)
Ändere das:
explore(path)
Zu diesem:
for subpath in explore(path):
yield subpath
Oder verwenden Sie os.walk
, wie von phooji vorgeschlagen (was die bessere Option ist).
Das nennt explore
wie eine Funktion. Was Sie tun sollten, ist iteriert wie ein Generator:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
else:
yield path
BEARBEITEN: Anstelle des Moduls stat
können Sie os.path.isdir(path)
verwenden.
Versuche dies:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
Sie können die Rekursion auch mit einem Stack implementieren.
Dies hat jedoch keinen Vorteil, außer dass es möglich ist. Wenn Sie Python in erster Linie verwenden, lohnt sich die Leistungssteigerung wahrscheinlich nicht.
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 ist ideal, wenn Sie alle Ordner und Unterordner durchsuchen müssen. Wenn Sie das nicht brauchen, ist es, als würde man mit einer Elefantenpistole eine Fliege töten.
Für diesen speziellen Fall könnte os.walk jedoch ein besserer Ansatz sein.
Um die ursprüngliche Frage zu beantworten, lautet der Schlüssel, dass die yield
-Anweisung aus der Rekursion heraus propagiert werden muss (genau wie return
). Hier ist eine funktionierende Neuimplementierung von os.walk()
. Ich verwende dies in einer Pseudo-VFS-Implementierung, bei der ich zusätzlich os.listdir()
und ähnliche Aufrufe ersetze.
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