12901 Stimmen

Was bewirkt das Schlüsselwort "yield"?

Wozu dient die yield Schlüsselwort in Python? Was bewirkt es?

Ich versuche zum Beispiel, folgenden Code zu verstehen 1 :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Und das ist der Anrufer:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Was passiert, wenn die Methode _get_child_candidates aufgerufen wird? Wird eine Liste zurückgegeben? Ein einzelnes Element? Wird sie erneut aufgerufen? Wann werden die nachfolgenden Aufrufe beendet?


1. Dieses Stück Code wurde von Jochen Schulz (jrschulz) geschrieben, der eine großartige Python-Bibliothek für metrische Räume entwickelt hat. Dies ist der Link zum vollständigen Quellcode: <a href="https://well-adjusted.de/~jrspieker/mspace/" rel="noreferrer">Modul mspace </a>.

90voto

Evgeni Sergeev Punkte 20596

Hier ist ein geistiges Bild von dem, was yield tut.

Ich stelle mir einen Thread gerne als einen Stapel vor (auch wenn er nicht auf diese Weise implementiert ist).

Wenn eine normale Funktion aufgerufen wird, legt sie ihre lokalen Variablen auf den Stack, führt einige Berechnungen durch, leert dann den Stack und kehrt zurück. Die Werte der lokalen Variablen werden nie wieder gesehen.

Mit einem yield Funktion, wenn ihr Code zu laufen beginnt (d. h. nachdem die Funktion aufgerufen wurde und ein Generatorobjekt zurückgibt, dessen next() Methode aufgerufen wird), legt er seine lokalen Variablen ebenfalls auf den Stack und rechnet eine Weile. Aber dann, wenn er auf die yield Anweisung, bevor sie ihren Teil des Stapels löscht und zurückkehrt, einen Schnappschuss ihrer lokalen Variablen macht und sie im Generatorobjekt speichert. Außerdem notiert er die Stelle in seinem Code, an der er sich gerade befindet (d.h. die bestimmte yield Anweisung).

Es handelt sich also um eine Art eingefrorene Funktion, an der der Generator hängt.

Lorsque next() aufgerufen wird, holt sie die Bestandteile der Funktion auf den Stapel und reanimiert sie. Die Funktion setzt ihre Berechnungen an der Stelle fort, an der sie aufgehört hat, ohne zu merken, dass sie gerade eine Ewigkeit im Kühlhaus verbracht hat.

Vergleichen Sie die folgenden Beispiele:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Wenn wir die zweite Funktion aufrufen, verhält sie sich ganz anders als die erste. Die yield Die Aussage mag unerreichbar sein, aber wenn sie irgendwo vorhanden ist, verändert sie die Art dessen, womit wir es zu tun haben.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Aufruf von yielderFunction() führt seinen Code nicht aus, sondern macht aus dem Code einen Generator. (Vielleicht ist es eine gute Idee, solche Dinge mit dem yielder Präfix zur besseren Lesbarkeit).

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

Le site gi_code y gi_frame sind die Felder, in denen der eingefrorene Zustand gespeichert wird. Durchsuchen Sie sie mit dir(..) können wir bestätigen, dass unser obiges mentales Modell glaubwürdig ist.

85voto

Rafael Punkte 6383

Stellen Sie sich vor, Sie haben eine bemerkenswerte Maschine entwickelt, die täglich Abertausende von Glühbirnen erzeugen kann. Die Maschine erzeugt diese Glühbirnen in Schachteln mit einer eindeutigen Seriennummer. Sie haben nicht genug Platz, um all diese Glühbirnen gleichzeitig zu lagern, also möchten Sie die Maschine so einstellen, dass sie Glühbirnen auf Abruf erzeugt.

Python-Generatoren unterscheiden sich nicht wesentlich von diesem Konzept. Stellen Sie sich vor, Sie haben eine Funktion namens barcode_generator die eindeutige Seriennummern für die Boxen generiert. Natürlich können Sie eine große Anzahl solcher Strichcodes von der Funktion zurückgeben lassen, sofern die Hardware (RAM) dies zulässt. Eine klügere und platzsparendere Option ist es, diese Seriennummern bei Bedarf zu erzeugen.

Der Code der Maschine:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1

barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

Beachten Sie die next(barcode) etwas.

Wie Sie sehen, haben wir eine in sich geschlossene "Funktion", um jedes Mal die nächste eindeutige Seriennummer zu erzeugen. Diese Funktion gibt eine Generator ! Wie Sie sehen können, rufen wir die Funktion nicht jedes Mal auf, wenn wir eine neue Seriennummer benötigen, sondern wir verwenden next() den Generator, um die nächste Seriennummer zu erhalten.

Faule Iteratoren

Genauer gesagt, ist dieser Generator ein träger Iterator ! Ein Iterator ist ein Objekt, das uns hilft, eine Folge von Objekten zu durchlaufen. Er heißt faule weil es nicht alle Elemente der Sequenz in den Speicher lädt, bis sie benötigt werden. Die Verwendung von next im vorherigen Beispiel ist die ausdrücklich um das nächste Element aus dem Iterator zu erhalten. Die implizit Weg ist die Verwendung von for-Schleifen:

for barcode in barcode_generator():
    print(barcode)

Auf diese Weise können Sie unendlich viele Strichcodes drucken, ohne dass Ihnen der Speicher ausgeht.

Mit anderen Worten, ein Generator sieht aus wie eine Funktion, sondern verhält sich wie einen Iterator.

Anwendung in der realen Welt?

Und schließlich: Anwendungen in der Praxis? Sie sind in der Regel nützlich, wenn Sie mit großen Sequenzen arbeiten. Stellen Sie sich vor, Sie lesen eine riesig Datei von der Festplatte mit Milliarden von Datensätzen. Das Einlesen der gesamten Datei in den Speicher, bevor Sie mit ihrem Inhalt arbeiten können, ist wahrscheinlich nicht machbar (d.h. der Speicher geht Ihnen aus).

81voto

Gavriel Cohen Punkte 3882

Ein einfaches Beispiel, um zu verstehen, was es ist: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2

for i in f123():
    print (i)

Die Ausgabe ist:

1 2 1 2 1 2 1 2

77voto

Wie jede Antwort nahelegt, yield wird für die Erstellung eines Sequenzgenerators verwendet. Er wird verwendet, um eine Sequenz dynamisch zu erzeugen. Wenn Sie zum Beispiel eine Datei zeilenweise über ein Netzwerk lesen, können Sie die Funktion yield wie folgt funktionieren:

def getNextLines():
   while con.isOpen():
       yield con.read()

Sie können ihn wie folgt in Ihrem Code verwenden:

for line in getNextLines():
    doSomeThing(line)

Ausführungskontrollübertragungen gotcha

Die Ausführungskontrolle wird von getNextLines() an die for Schleife, wenn Yield ausgeführt wird. Daher beginnt die Ausführung bei jedem Aufruf von getNextLines() an der Stelle, an der sie das letzte Mal unterbrochen wurde.

Kurz gesagt, eine Funktion mit folgendem Code

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

wird gedruckt

"first time"
"second time"
"third time"
"Now some useful value 12"

75voto

smwikipedia Punkte 56976

(Meine folgende Antwort bezieht sich nur auf die Verwendung des Python-Generators, nicht auf die zugrundeliegende Implementierung des Generatormechanismus , was einige Tricks der Stack- und Heap-Manipulation beinhaltet).

Lorsque yield wird anstelle einer return in einer Python-Funktion, wird diese Funktion in etwas Besonderes namens generator function . Diese Funktion gibt ein Objekt der generator Typ. Le site yield ist ein Flag, mit dem der Python-Compiler angewiesen wird, solche Funktionen besonders zu behandeln. Normale Funktionen werden beendet, sobald ein Wert von ihnen zurückgegeben wird. Aber mit Hilfe des Compilers kann die Generatorfunktion kann gedacht werden als als wiederaufnehmbar. Das heißt, der Ausführungskontext wird wiederhergestellt und die Ausführung wird ab dem letzten Lauf fortgesetzt. Solange Sie nicht explizit return aufrufen, was zu einer StopIteration Ausnahme (die auch Teil des Iterator-Protokolls ist), oder das Ende der Funktion erreichen. Ich habe eine Menge Referenzen gefunden über generator aber das eine von der functional programming perspective ist am besten verdaulich.

(Jetzt möchte ich über die Gründe sprechen, die hinter generator und die iterator auf der Grundlage meines eigenen Verständnisses. Ich hoffe, dies kann Ihnen helfen, die wesentliche Motivation von Iterator und Generator. Dieses Konzept taucht auch in anderen Sprachen auf, z. B. in C#).

Wenn wir ein Bündel von Daten verarbeiten wollen, speichern wir die Daten normalerweise zuerst irgendwo und verarbeiten sie dann einzeln. Aber das naiv Ansatz ist problematisch. Wenn die Datenmenge sehr groß ist, ist es teuer, sie vorher als Ganzes zu speichern. Anstatt also die data selbst direkt zu speichern, warum nicht eine Art von metadata indirekt, d.h. the logic how the data is computed .

Es gibt 2 Möglichkeiten, solche Metadaten zu verpacken.

  1. Der OO-Ansatz, wir verpacken die Metadaten as a class . Dies ist die sogenannte iterator der das Iteratorprotokoll implementiert (d. h. die __next__() et __iter__() Methoden). Dies ist auch die häufig anzutreffende Iterator-Entwurfsmuster .
  2. Der funktionale Ansatz, wir verpacken die Metadaten as a function . Dies ist die sogenannte generator function . Aber unter der Motorhaube, die zurückgegeben generator object immer noch IS-A Iterator, weil er auch das Iteratorprotokoll implementiert.

In jedem Fall wird ein Iterator erstellt, d. h. ein Objekt, das Ihnen die gewünschten Daten liefern kann. Der OO-Ansatz kann etwas komplexer sein. Wie auch immer, es liegt an Ihnen, welche Methode Sie verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X