wake-up-neo.com

Ausbeute in rekursiver Funktion

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?

50
Ali

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
26
phooji

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.

124
Jeremy Banks

Das Problem ist diese Codezeile:

explore(path)

Was tut es?

  • ruft explore mit der neuen path auf
  • explore läuft und erstellt einen Generator
  • der Generator wird an die Stelle zurückgegeben, an der explore(path) ausgeführt wurde. . .
  • und wird verworfen

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)
35
Ethan Furman

Ä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).

8
Dietrich Epp

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.

3
MRAB

Versuche dies:

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

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('.'))
0
user1149913

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.

0
Robson França

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
0
Hlórriði