Ich habe naiv versucht, einen rekursiven Generator zu erstellen. Hat nicht funktioniert. Das habe ich getan:
def recursive_generator(lis):
yield lis[0]
recursive_generator(lis[1:])
for k in recursive_generator([6,3,9,1]):
print(k)
Alles was ich bekam war der erste Gegenstand 6
.
Gibt es eine Möglichkeit, solchen Code zum Laufen zu bringen? Im Wesentlichen den yield
Befehl in einem Rekursionsschema auf die darüber liegende Ebene übertragen?
yield from another_generator()
jedes Element einzeln oder explizit in der while-Schleife ausgeben. Obanother_generator()
es in Ihrer Terminologie "rekursiv" ist oder nicht - das spielt keine Rolle.Antworten:
Versuche dies:
def recursive_generator(lis): yield lis[0] yield from recursive_generator(lis[1:]) for k in recursive_generator([6,3,9,1]): print(k)
Ich sollte darauf hinweisen, dass dies aufgrund eines Fehlers in Ihrer Funktion nicht funktioniert. Es sollte wahrscheinlich einen Scheck enthalten, der
lis
nicht leer ist, wie unten gezeigt:def recursive_generator(lis): if lis: yield lis[0] yield from recursive_generator(lis[1:])
Wenn Sie Python 2.7 verwenden und dies nicht tun
yield from
, überprüfen Sie diese Frage.quelle
Warum Ihr Code den Job nicht gemacht hat
In Ihrem Code ist die Generatorfunktion:
Die zweite Instanz des Iterators, die rekursiv erstellt wurde , wird niemals wiederholt. Deshalb haben Sie nur den ersten Punkt der Liste erhalten.
Eine Generatorfunktion ist nützlich, um automatisch ein Iteratorobjekt zu erstellen (ein Objekt, das das Iteratorprotokoll implementiert ), aber dann müssen Sie darüber iterieren: entweder manuell die
next()
Methode für das Objekt aufrufen oder mithilfe einer Schleifenanweisung, die das automatisch verwendet Iteratorprotokoll.Können wir also rekursiv einen Generator aufrufen?
Die Antwort lautet ja . Zurück zu Ihrem Code: Wenn Sie dies wirklich mit einer Generatorfunktion tun möchten, können Sie wahrscheinlich Folgendes versuchen:
def recursive_generator(some_list): """ Return some_list items, one at a time, recursively iterating over a slice of it... """ if len(some_list)>1: # some_list has more than one item, so iterate over it for i in recursive_generator(some_list[1:]): # recursively call this generator function to iterate over a slice of some_list. # return one item from the list. yield i else: # the iterator returned StopIteration, so the for loop is done. # to finish, return the only value not included in the slice we just iterated on. yield some_list[0] else: # some_list has only one item, no need to iterate on it. # just return the item. yield some_list[0] some_list = [6,3,9,1] for k in recursive_generator(some_list): print(k)
Hinweis: Die Artikel werden in umgekehrter Reihenfolge zurückgegeben. Sie sollten sie daher verwenden,
some_list.reverse()
bevor Sie den Generator zum ersten Mal aufrufen.In diesem Beispiel ist Folgendes zu beachten: Die Generatorfunktion ruft sich rekursiv in einer for- Schleife auf , die einen Iterator sieht und automatisch das Iterationsprotokoll verwendet, sodass tatsächlich Werte daraus abgerufen werden.
Das funktioniert, aber ich denke, das ist wirklich nicht nützlich . Wir verwenden eine Generatorfunktion, um eine Liste zu durchlaufen und die Elemente einzeln herauszuholen, aber ... eine Liste ist selbst iterierbar, sodass keine Generatoren erforderlich sind! Natürlich verstehe ich das, dies ist nur ein Beispiel, vielleicht gibt es nützliche Anwendungen dieser Idee.
Ein anderes Beispiel
Lassen Sie uns das vorherige Beispiel (für Faulheit) recyceln. Nehmen wir an, wir müssen die Elemente in einer Liste drucken und jedem Element die Anzahl der vorherigen Elemente hinzufügen (nur ein zufälliges Beispiel, das nicht unbedingt nützlich ist).
Der Code wäre:
def recursive_generator(some_list): """ Return some_list items, one at a time, recursively iterating over a slice of it... and adding to every item the count of previous items in the list """ if len(some_list)>1: # some_list has more than one item, so iterate over it for i in recursive_generator(some_list[1:]): # recursively call this generator function to iterate over a slice of some_list. # return one item from the list, but add 1 first. # Every recursive iteration will add 1, so we basically add the count of iterations. yield i + 1 else: # the iterator returned StopIteration, so the for loop is done. # to finish, return the only value not included in the slice we just iterated on. yield some_list[0] else: # some_list has only one item, no need to iterate on it. # just return the item. yield some_list[0] some_list = [6,3,9,1] for k in recursive_generator(some_list): print(k)
Wie Sie sehen, führt die Generatorfunktion tatsächlich etwas aus, bevor Listenelemente zurückgegeben werden, UND die Verwendung der Rekursion macht Sinn. Trotzdem nur ein dummes Beispiel, aber Sie bekommen die Idee.
Hinweis: Natürlich wird in diesem dummen Beispiel erwartet, dass die Liste nur Zahlen enthält. Wenn Sie wirklich versuchen möchten, es zu brechen, fügen Sie einfach einen String in some_list ein und haben Sie Spaß. Auch dies ist nur ein Beispiel, kein Produktionscode !
quelle
Rekursive Generatoren sind nützlich, um nichtlineare Strukturen zu durchlaufen. Angenommen, ein Binärbaum ist entweder Keine oder ein Wertetupel, linker Baum, rechter Baum. Ein rekursiver Generator ist der einfachste Weg, alle Knoten zu besuchen. Beispiel:
tree = (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))), (6, None, (7, (8, (9, None, None), None), None))) def visit(tree): # if tree is not None: try: value, left, right = tree except ValueError: # wrong number to unpack print("Bad tree:", tree) else: # The following is one of 3 possible orders. yield from visit(left) yield value # Put this first or last for different orders. yield from visit(right) print(list(visit(tree))) # prints nodes in the correct order for 'yield value' in the middle. # [1, 3, 2, 5, 4, 0, 6, 9, 8, 7]
Bearbeiten: Ersetzen
if tree
durchif tree is not None
, um andere falsche Werte als Fehler abzufangen.Bearbeiten 2: Informationen zum Einfügen der rekursiven Aufrufe in die try: -Klausel (Kommentar von @ jpmc26).
Bei fehlerhaften Knoten protokolliert der obige Code nur den ValueError und fährt fort. Wenn zum Beispiel
(9,None,None)
durch ersetzt wird(9,None)
, ist die AusgabeBad tree: (9, None) [1, 3, 2, 5, 4, 0, 6, 8, 7]
Typischer wäre es, nach der Protokollierung erneut zu erhöhen, sodass die Ausgabe erfolgt
Bad tree: (9, None) Traceback (most recent call last): File "F:\Python\a\tem4.py", line 16, in <module> print(list(visit(tree))) File "F:\Python\a\tem4.py", line 14, in visit yield from visit(right) File "F:\Python\a\tem4.py", line 14, in visit yield from visit(right) File "F:\Python\a\tem4.py", line 12, in visit yield from visit(left) File "F:\Python\a\tem4.py", line 12, in visit yield from visit(left) File "F:\Python\a\tem4.py", line 7, in visit value, left, right = tree ValueError: not enough values to unpack (expected 3, got 2)
Der Traceback gibt den Pfad von der Wurzel zum fehlerhaften Knoten an. Man könnte den ursprünglichen
visit(tree)
Aufruf umbrechen, um den Traceback auf den Pfad zu reduzieren: (root, right, right, left, left).Wenn die rekursiven Aufrufe in der try: -Klausel enthalten sind, wird der Fehler auf jeder Ebene des Baums erneut erfasst, protokolliert und erneut ausgeführt.
Bad tree: (9, None) Bad tree: (8, (9, None), None) Bad tree: (7, (8, (9, None), None), None) Bad tree: (6, None, (7, (8, (9, None), None), None)) Bad tree: (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))), (6, None, (7, (8, (9, None), None), None))) Traceback (most recent call last): ... # same as before
Die mehreren Protokollierungsberichte sind wahrscheinlich mehr Rauschen als Hilfe. Wenn der Pfad zum fehlerhaften Knoten gewünscht wird, ist es möglicherweise am einfachsten, jeden rekursiven Aufruf in einer eigenen try: -Klausel zu verpacken und auf jeder Ebene einen neuen ValueError mit dem bisher erstellten Pfad auszulösen.
Schlussfolgerung: Wenn keine Ausnahme für die Flusskontrolle verwendet wird (wie dies beispielsweise bei IndexError der Fall ist), hängt das Vorhandensein und die Platzierung von try: -Anweisungen von der gewünschten Fehlerberichterstattung ab.
quelle
else
Block auf demtry
/except
; Es wäre einfacher, diesen Code einfach in dentry
Block zu verschieben, nicht wahr?Bis zu Python 3.4 musste eine Generatorfunktion eine
StopIteration
Ausnahme auslösen, wenn sie fertig war. Für den rekursiven Fall werden andere Ausnahmen (z. B.IndexError
) früher als ausgelöstStopIteration
, daher fügen wir sie manuell hinzu.def recursive_generator(lis): if not lis: raise StopIteration yield lis[0] yield from recursive_generator(lis[1:]) for k in recursive_generator([6, 3, 9, 1]): print(k)
def recursive_generator(lis): if not lis: raise StopIteration yield lis.pop(0) yield from recursive_generator(lis) for k in recursive_generator([6, 3, 9, 1]): print(k)
Beachten Sie, dass die
for
Schleife eineStopIteration
Ausnahme abfängt . Mehr dazu hierquelle
Der Grund, warum Ihr rekursiver Aufruf nur einmal ausgeführt wird, besteht darin, dass Sie im Wesentlichen verschachtelte Generatoren erstellen. Das heißt, Sie erstellen jedes Mal, wenn Sie die Funktion recursive_generator rekursiv aufrufen, einen neuen Generator in einem Generator.
Versuchen Sie Folgendes und Sie werden sehen.
def recursive_generator(lis): yield lis[0] yield recursive_generator(lis[1:]) for k in recursive_generator([6,3,9,1]): print(type(k))
Eine einfache Lösung ist, wie andere erwähnen, die Verwendung
yield from
.quelle
Ja, Sie können rekursive Generatoren haben. Sie leiden jedoch unter der gleichen Rekursionstiefengrenze wie andere rekursive Funktionen.
def recurse(x): yield x yield from recurse(x) for (i, x) in enumerate(recurse(5)): print(i, x)
Diese Schleife erreicht vor dem Absturz ungefähr 3000 (für mich).
Mit einigen Tricks können Sie jedoch eine Funktion erstellen, die sich selbst einen Generator zuführt. Auf diese Weise können Sie Generatoren so schreiben, als wären sie rekursiv, jedoch nicht: https://gist.github.com/3noch/7969f416d403ba3a54a788b113c204ce
quelle