Frage Was macht das Keyword "yield"?


Was nützt das? yield Stichwort in Python? Was tut es?

Zum Beispiel versuche ich diesen Code zu verstehen1:

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 bei der Methode? _get_child_candidates wird genannt? Wird eine Liste zurückgegeben? Ein einzelnes Element? Wird es noch einmal angerufen? Wann werden nachfolgende Anrufe gestoppt?


1. Der Code stammt von Jochen Schulz (jrschulz), der eine großartige Python-Bibliothek für metrische Räume erstellt hat. Dies ist der Link zur vollständigen Quelle: Modul Mspace.


8311
2017-10-23 22:21


Ursprung


Antworten:


Um was zu verstehen yield muss man verstehen was Generatoren sind. Und bevor Generatoren kommen iterierbar.

Iterables

Wenn Sie eine Liste erstellen, können Sie ihre Elemente nacheinander lesen. Das Lesen der einzelnen Elemente wird Iteration genannt:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist ist ein iterierbar. Wenn Sie ein Listenverständnis verwenden, erstellen Sie eine Liste und damit ein iterables:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Alles was du benutzen kannst "for... in..."ist ein iterabler; lists, strings, Dateien ...

Diese Iterables sind praktisch, weil Sie sie so oft lesen können, wie Sie möchten, aber Sie speichern alle Werte im Speicher, und das ist nicht immer das, was Sie wollen, wenn Sie viele Werte haben.

Generatoren

Generatoren sind Iteratoren, eine Art von iterierbar Sie können nur einmal iterieren. Generatoren speichern nicht alle Werte im Speicher, Sie generieren die Werte im laufenden Betrieb:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Es ist genauso, außer dass du es benutzt hast () Anstatt von []. Aber du kann nicht ausführen for i in mygenerator ein zweites Mal, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, vergessen dann und berechnen 1 und beenden die Berechnung von 4, eins nach dem anderen.

Ausbeute

yield ist ein Schlüsselwort, das wie verwendet wird return, außer dass die Funktion einen Generator zurückgibt.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Hier ist es ein nutzloses Beispiel, aber es ist praktisch, wenn Sie wissen, dass Ihre Funktion eine große Menge von Werten zurückgibt, die Sie nur einmal lesen müssen.

Meistern yieldDas musst du verstehen Wenn Sie die Funktion aufrufen, wird der Code, den Sie in den Funktionskörper geschrieben haben, nicht ausgeführt. Die Funktion gibt nur das Generator-Objekt zurück, das ist ein bisschen knifflig :-)

Dann wird Ihr Code jedes Mal ausgeführt for benutzt den Generator.

Jetzt der schwierige Teil:

Das erste Mal das for ruft das Generatorobjekt auf, das von Ihrer Funktion erstellt wurde, und führt den Code in Ihrer Funktion von Anfang bis zum Treffer aus yield, dann wird der erste Wert der Schleife zurückgegeben. Dann führt jeder andere Aufruf die Schleife, die Sie in der Funktion geschrieben haben, noch einmal aus und gibt den nächsten Wert zurück, bis kein Wert mehr zurückgegeben wird.

Der Generator wird als leer betrachtet, sobald die Funktion ausgeführt wird, aber nicht yield nicht mehr. Es kann sein, weil die Schleife zu einem Ende gekommen ist, oder weil Sie nicht zufrieden sind "if/else" nicht mehr.


Dein Code erklärt

Generator:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Anrufer:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Dieser Code enthält mehrere intelligente Teile:

  • Die Schleife iteriert auf einer Liste, aber die Liste wird erweitert, während die Schleife iteriert wird :-) Es ist eine knappe Möglichkeit, all diese verschachtelten Daten zu durchlaufen, auch wenn es ein bisschen gefährlich ist, da Sie eine Endlosschleife haben können. In diesem Fall, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) erschöpft alle Werte des Generators, aber while erstellt immer neue Generatorobjekte, die andere Werte als die vorherigen erzeugen, da sie nicht auf demselben Knoten angewendet werden.

  • Das extend() Methode ist eine Listenobjektmethode, die ein iterables erwartet und ihre Werte der Liste hinzufügt.

Normalerweise übergeben wir eine Liste an sie:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Aber in deinem Code bekommt es einen Generator, was gut ist, weil:

  1. Sie müssen die Werte nicht zweimal lesen.
  2. Sie haben möglicherweise viele Kinder und möchten nicht, dass sie alle im Speicher gespeichert werden.

Und es funktioniert, weil es Python egal ist, ob das Argument einer Methode eine Liste ist oder nicht. Python erwartet Iterables, damit es mit Strings, Listen, Tupeln und Generatoren funktioniert! Das nennt man Enten-Typisierung und ist einer der Gründe, warum Python so cool ist. Aber das ist eine andere Geschichte, für eine andere Frage ...

Sie können hier anhalten oder ein wenig lesen, um eine erweiterte Verwendung eines Generators zu sehen:

Die Erschöpfung eines Generators kontrollieren

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Hinweis: Verwenden Sie für Python 3print(corner_street_atm.__next__()) oder print(next(corner_street_atm))

Es kann für verschiedene Dinge nützlich sein, wie den Zugriff auf eine Ressource zu kontrollieren.

Itertools, dein bester Freund

Das Itertools-Modul enthält spezielle Funktionen zum Bearbeiten von Iterablen. Möchten Sie jemals einen Generator duplizieren? Kette zwei Generatoren? Gruppenwerte in einer verschachtelten Liste mit einem Einzeiler? Map / Zip ohne eine andere Liste zu erstellen?

Dann einfach import itertools.

Ein Beispiel? Lassen Sie uns die möglichen Ankunftsanweisungen für ein Vier-Pferderennen sehen:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Die inneren Mechanismen der Iteration verstehen

Iteration ist ein Prozess, der Iterables impliziert (Implementierung des __iter__() Methode) und Iteratoren (Implementierung der __next__() Methode). Iterables sind Objekte, von denen Sie einen Iterator erhalten können. Iteratoren sind Objekte, mit denen Sie Iterabeln iterieren können.

Es gibt mehr darüber in diesem Artikel Wie for Schleifen funktionieren.


12173
2017-10-23 22:48



Abkürzung nach Grokking  yield

Wenn Sie eine Funktion mit sehen yield Aussagen, wenden Sie diesen einfachen Trick an, um zu verstehen, was passieren wird:

  1. Fügen Sie eine Linie ein result = [] zu Beginn der Funktion.
  2. Ersetzen Sie jedes yield expr mit result.append(expr).
  3. Fügen Sie eine Linie ein return result am unteren Rand der Funktion.
  4. Yay - nicht mehr yield Aussagen! Lesen und verstehen Sie den Code.
  5. Funktion mit der ursprünglichen Definition vergleichen

Dieser Trick kann Ihnen eine Vorstellung von der Logik hinter der Funktion geben, aber was tatsächlich passiert yield unterscheidet sich wesentlich von dem, was im listenbasierten Ansatz passiert. In vielen Fällen wird der Ertragsansatz viel effizienter und schneller sein. In anderen Fällen bringt Sie dieser Trick in eine Endlosschleife, obwohl die ursprüngliche Funktion einwandfrei funktioniert. Lesen Sie weiter, um mehr zu erfahren ...

Verwechsle deine Iterables, Iteratoren und Generatoren nicht

Zuerst die Iteratorprotokoll - wenn du schreibst

for x in mylist:
    ...loop body...

Python führt die folgenden zwei Schritte aus:

  1. Ruft einen Iterator für mylist:

    Anruf iter(mylist) -> Dies gibt ein Objekt mit einem zurück next() Methode (oder __next__() in Python 3).

    [Dies ist der Schritt, den die meisten Menschen vergessen, Ihnen zu erzählen]

  2. Verwendet den Iterator, um Elemente zu durchlaufen:

    Rufen Sie immer wieder an next() Methode auf dem Iterator von Schritt 1 zurückgegeben. Der Rückgabewert von next() ist zugeordnet x und der Schleifenkörper wird ausgeführt. Wenn eine Ausnahme vorliegt StopIteration wird von innen angehoben next()Dies bedeutet, dass im Iterator keine Werte mehr vorhanden sind und die Schleife beendet wird.

Die Wahrheit ist, dass Python die obigen zwei Schritte ausführt, wann immer es will Schleife über der Inhalt eines Objekts - so könnte es eine for-Schleife sein, aber es könnte auch Code-ähnlich sein otherlist.extend(mylist) (woher otherlist ist eine Python-Liste).

Hier mylist ist ein iterierbar weil es das Iterator-Protokoll implementiert. In einer benutzerdefinierten Klasse können Sie das implementieren __iter__() Methode, um Instanzen Ihrer Klasse iterierbar zu machen. Diese Methode sollte einen zurückgeben Iterator. Ein Iterator ist ein Objekt mit einem next() Methode. Es ist möglich, beides zu implementieren __iter__() und next() auf der gleichen Klasse und haben __iter__() Rückkehr self. Dies funktioniert in einfachen Fällen, aber nicht, wenn zwei Iteratoren gleichzeitig über dasselbe Objekt laufen sollen.

Das ist das Iteratorprotokoll, viele Objekte implementieren dieses Protokoll:

  1. Built-in-Listen, Wörterbücher, Tupel, Sets, Dateien.
  2. Benutzerdefinierte Klassen, die implementieren __iter__().
  3. Generatoren.

Beachten Sie, dass a for Die Schleife weiß nicht, um was für ein Objekt es sich handelt - sie folgt nur dem Iterator-Protokoll und freut sich, Element für Element beim Aufruf zu erhalten next(). Built-in-Listen geben ihre Elemente nacheinander zurück, Wörterbücher geben das zurück Schlüssel Nacheinander geben Dateien die Linien eins nach dem anderen usw. Und Generatoren kehren zurück ... nun, das ist wo yield kommt herein:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Anstatt von yield Aussagen, wenn Sie drei hatten return Aussagen in f123() nur der erste würde ausgeführt werden, und die Funktion würde verlassen. Aber f123() ist keine gewöhnliche Funktion. Wann f123() heißt es nicht gib irgendeinen der Werte in den Yield Statements zurück! Es gibt ein Generatorobjekt zurück. Außerdem wird die Funktion nicht wirklich beendet - sie geht in einen angehaltenen Zustand über. Wenn das for Englisch: www.weisang.info/index.php?id=143&t...h=fbcdcf8cf6 Die Schleife versucht, das Generatorobjekt zu durchlaufen, die Funktion wird in der nächsten Zeile nach dem Zustand wieder fortgesetzt yield Von dem es vorher zurückkam, führt er die nächste Codezeile aus, in diesem Fall a yield Anweisung und gibt das als nächstes Element zurück. Dies geschieht, bis die Funktion beendet wird und der Generator ansteigt StopIterationund die Schleife wird beendet.

Das Generator-Objekt ist also wie ein Adapter - an einem Ende zeigt es das Iterator-Protokoll durch Belichtung __iter__() und next() Methoden, um die for Schleife glücklich. Am anderen Ende jedoch führt es die Funktion gerade genug aus, um den nächsten Wert daraus zu erhalten, und setzt es in den gesperrten Modus zurück.

Warum Generatoren verwenden?

Normalerweise können Sie Code schreiben, der keine Generatoren verwendet, sondern dieselbe Logik implementiert. Eine Möglichkeit ist es, die temporäre Liste "Trick" zu verwenden, die ich zuvor erwähnt habe. Das wird nicht in allen Fällen funktionieren, z.B. Wenn Sie über Endlosschleifen verfügen oder wenn Sie eine wirklich lange Liste haben, wird der Speicher möglicherweise ineffizient genutzt. Der andere Ansatz besteht darin, eine neue iterierbare Klasse zu implementieren SomethingIter Das behält den Status in Instanzmembern bei und führt den nächsten logischen Schritt aus next() (oder __next__() in Python 3) Methode. Abhängig von der Logik, der Code innerhalb der next() Methode kann am Ende sehr komplex aussehen und anfällig für Fehler sein. Hier bieten Generatoren eine saubere und einfache Lösung.


1635
2017-10-25 21:22



Denk darüber so:

Ein Iterator ist nur ein klingender Ausdruck für ein Objekt mit einer next () -Methode. Eine Yield-Ed-Funktion endet also in etwa so:

Originalfassung:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Dies ist im Grunde, was der Python-Interpreter mit dem obigen Code macht:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Um mehr darüber zu erfahren, was sich hinter den Kulissen abspielt, ist die for Schleife kann zu diesem umgeschrieben werden:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Macht das mehr Sinn oder verwirrt Sie mehr? :)

Ich sollte das beachten ist eine übermäßige Vereinfachung für illustrative Zwecke. :)


396
2017-10-23 22:28



Das yield Das Keyword ist auf zwei einfache Fakten reduziert:

  1. Wenn der Compiler die yield Stichwort irgendwo Innerhalb einer Funktion kehrt diese Funktion nicht mehr über die Funktion zurück return Erklärung. Stattdessenes sofort gibt a zurück faul "ausstehende Liste" -Objekt rief einen Generator an
  2. Ein Generator ist iterierbar. Was ist ein iterierbar? Es ist alles wie ein list oder set oder range oder dict-view, mit a Eingebautes Protokoll für den Besuch jedes Elements in einer bestimmten Reihenfolge.

In einer Nussschale: Ein Generator ist eine faule, inkrementell ausstehende Liste, und yield Mit den Anweisungen können Sie die Funktionsnotation verwenden, um die Listenwerte zu programmieren Der Generator sollte schrittweise ausspucken.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Beispiel

Lassen Sie uns eine Funktion definieren makeRange das ist genau wie bei Python range. Berufung makeRange(n) Gibt einen Generator zurück:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Um zu erzwingen, dass der Generator seine anstehenden Werte sofort zurückgibt, können Sie ihn übergeben list() (genau wie Sie könnten alle iterable):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Vergleich mit "nur eine Liste zurückgeben"

Das obige Beispiel kann so aussehen, als ob Sie nur eine Liste erstellen, an die Sie sich anhängen und zurückgeben:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Es gibt jedoch einen großen Unterschied; siehe den letzten Abschnitt.


Wie könnten Sie Generatoren verwenden?

Ein iterabler Wert ist der letzte Teil eines Listenverständnisses, und alle Generatoren sind iterierbar, weshalb sie oft wie folgt verwendet werden:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Um ein besseres Gefühl für Generatoren zu bekommen, können Sie mit dem itertools Modul (sicher sein zu verwenden chain.from_iterable eher, als chain wenn es gerechtfertigt ist). Zum Beispiel könnten Sie sogar Generatoren verwenden, um unendlich lange faule Listen zu implementieren itertools.count(). Sie könnten Ihre eigenen implementieren def enumerate(iterable): zip(count(), iterable)oder alternativ mit dem yieldSchlüsselwort in einer while-Schleife.

Bitte beachten Sie: Generatoren können tatsächlich für viele weitere Dinge verwendet werden, wie z Coroutinen implementieren oder nicht-deterministische Programmierung oder andere elegante Dinge. Der hier verwendete "faule Listen" -Aspekt ist jedoch der häufigste, den Sie finden werden.


Hinter den Kulissen

So funktioniert das "Python-Iterationsprotokoll". Das heißt, was passiert, wenn Sie es tun list(makeRange(5)). Das ist, was ich früher als "faul, inkrementelle Liste" beschrieben habe.

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Die eingebaute Funktion next() ruft nur die Objekte an .next() Funktion, die ein Teil des "Iterationsprotokolls" ist und auf allen Iteratoren gefunden wird. Sie können manuell die next() Funktion (und andere Teile des Iterationsprotokolls), um ausgefallene Dinge zu implementieren, normalerweise auf Kosten der Lesbarkeit, also versuchen Sie es zu vermeiden ...


Minutien

Normalerweise würden die meisten Leute die folgenden Unterscheidungen nicht interessieren und wahrscheinlich hier aufhören zu lesen.

In Python-Sprache, ein iterierbar ist irgendein Objekt, das "das Konzept einer For-Schleife" wie eine Liste versteht [1,2,3], und ein Iterator ist eine spezifische Instanz der angeforderten for-Schleife [1,2,3].__iter__(). EIN Generator ist genau wie jeder Iterator, abgesehen von der Art, wie er geschrieben wurde (mit der Funktionssyntax).

Wenn Sie einen Iterator von einer Liste anfordern, erstellt er einen neuen Iterator. Wenn Sie jedoch einen Iterator von einem Iterator anfordern (was Sie selten tun würden), erhalten Sie nur eine Kopie von sich selbst.

In dem unwahrscheinlichen Fall, dass Sie etwas nicht tun ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... dann erinnere dich, dass ein Generator ein ist Iterator; das heißt, es ist einmalig. Wenn Sie es wiederverwenden möchten, sollten Sie anrufen myRange(...) nochmal. Wenn Sie das Ergebnis zweimal verwenden müssen, konvertieren Sie das Ergebnis in eine Liste und speichern Sie es in einer Variablen x = list(myRange(5)). Diejenigen, die unbedingt einen Generator klonen müssen (zum Beispiel, die eine erschreckend hechelnde Metaprogrammierung machen) können damit arbeiten itertools.tee wenn unbedingt notwendig, da der kopierbare Iterator Python PEP Standardvorschlag wurde verschoben.


346
2018-06-19 06:33



Was macht das? yield Stichwort in Python tun?

Antwort Gliederung / Zusammenfassung

  • Eine Funktion mit yield, wenn es heißt, gibt a zurück Generator.
  • Generatoren sind Iteratoren, weil sie das implementieren Iteratorprotokoll, so können Sie über sie iterieren.
  • Ein Generator kann auch sein Informationen gesendet, es begrifflich a Koroutine.
  • In Python 3 können Sie delegieren von einem Generator zum anderen in beiden Richtungen mit yield from.
  • (Anhang kritisiert ein paar Antworten, einschließlich der obersten, und diskutiert die Verwendung von return in einem Generator.)

Generatoren:

yield ist nur innerhalb einer Funktionsdefinition erlaubt, und die Einbeziehung von yield In einer Funktionsdefinition wird ein Generator zurückgegeben.

Die Idee für Generatoren kommt aus anderen Sprachen (siehe Fußnote 1) mit unterschiedlichen Implementierungen. In Pythons Generatoren ist die Ausführung des Codes gefroren an der Stelle des Ertrags. Wenn der Generator aufgerufen wird (die Methoden werden unten besprochen), wird die Ausführung fortgesetzt und erstarrt bei der nächsten Ausbeute.

yield bietet ein einfacher Weg Implementieren des Iteratorprotokolls, definiert durch die folgenden zwei Methoden: __iter__ und next (Python 2) oder __next__ (Python 3). Beide Methoden Machen Sie ein Objekt zu einem Iterator, den Sie eintippen könnten Iterator Abstrakte Basis Klasse von der collections Modul.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Der Generatortyp ist ein Untertyp des Iterators:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Und wenn nötig, können wir das wie folgt überprüfen:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Ein Merkmal eines Iterator  ist das einmal erschöpft, Sie können es nicht wiederverwenden oder zurücksetzen:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Sie müssen einen anderen erstellen, wenn Sie die Funktionalität erneut verwenden möchten (siehe Fußnote 2):

>>> list(func())
['I am', 'a generator!']

Man kann Daten programmatisch liefern, zum Beispiel:

def func(an_iterable):
    for item in an_iterable:
        yield item

Der obige einfache Generator ist auch äquivalent zu den folgenden - wie von Python 3.3 (und nicht in Python 2 verfügbar), die Sie verwenden können yield from:

def func(an_iterable):
    yield from an_iterable

Jedoch, yield from ermöglicht auch die Delegation an Subgeneratoren, Dies wird im folgenden Abschnitt zur kooperativen Delegation mit Sub-Coroutinen erläutert.

Routinen:

yield bildet einen Ausdruck, der das Senden von Daten in den Generator ermöglicht (siehe Fußnote 3)

Hier ist ein Beispiel, beachten Sie die received Variable, die auf die Daten verweist, die an den Generator gesendet werden:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Zuerst müssen wir den Generator mit der eingebauten Funktion anstellen, next. Es wird rufe das passende an next oder __next__ Methode, abhängig von der Version von Python, das du verwendest:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Und jetzt können wir Daten in den Generator senden. (Senden None ist das gleiche wie das Anrufen next.):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Cooperative Delegation zur Unterroutine mit yield from

Nun erinnere dich daran yield from ist in Python 3 verfügbar. Damit können wir delegieren Koroutinen zu einer Subcoroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

Und jetzt können wir Funktionalität an einen Sub-Generator delegieren und es kann verwendet werden durch einen Generator wie oben:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Sie können mehr über die genaue Semantik von lesen yield from im PEP 380.

Andere Methoden: Schließen und werfen

Das close Methode erhöht GeneratorExit an dem Punkt die Funktion die Ausführung wurde eingefroren. Dies wird auch von aufgerufen werden __del__ so du kann einen beliebigen Bereinigungscode eingeben, mit dem Sie umgehen GeneratorExit:

>>> my_account.close()

Sie können auch eine Ausnahme auslösen, die im Generator behandelt werden kann oder an den Benutzer weitergegeben:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Fazit

Ich glaube, ich habe alle Aspekte der folgenden Frage behandelt:

Was macht das? yield Stichwort in Python tun?

Es stellt sich heraus, dass yield macht viel. Ich bin mir sicher, ich könnte noch mehr hinzufügen gründliche Beispiele dazu. Wenn Sie mehr wollen oder etwas konstruktive Kritik haben, lassen Sie es mich wissen, indem Sie kommentieren unten.


Blinddarm:

Kritik an der Spitze / angenommene Antwort **

  • Es ist verwirrt darüber, was ein macht iterierbarVerwenden Sie einfach eine Liste als Beispiel. Siehe meine obigen Referenzen, aber zusammenfassend: Ein iterables hat ein __iter__ Methode, die ein zurückgibt Iterator. Ein Iterator bietet ein .next (Python 2 oder .__next__ (Python 3) Methode, die implizit von aufgerufen wird forSchleifen, bis es ansteigt StopIterationund sobald es so ist, wird es dies auch weiterhin tun.
  • Es verwendet dann einen Generatorausdruck, um zu beschreiben, was ein Generator ist. Da ein Generator ist einfach eine bequeme Möglichkeit, ein zu erstellen Iterator, es verwirrt nur die Sache, und wir sind noch nicht an die yield Teil.
  • Im Die Erschöpfung eines Generators kontrollieren er ruft an .next Methode, wenn er stattdessen die eingebaute Funktion verwenden soll, next. Es wäre eine angemessene Indirektionsschicht, weil sein Code in Python 3 nicht funktioniert.
  • Itertools? Dies war für was nicht relevant yield geht überhaupt.
  • Keine Diskussion der Methoden, die yield bietet zusammen mit der neuen Funktionalität yield from in Python 3. Die oberste / akzeptierte Antwort ist eine sehr unvollständige Antwort.

Kritik der Antwort suggeriert yield in einem Generator Ausdruck oder Verständnis.

Die Grammatik erlaubt derzeit jeden Ausdruck in einem Listenverständnis.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Da Ertrag ein Ausdruck ist, wurde er von einigen als interessant erachtet, ihn in der Übersicht oder im Generatorausdruck zu verwenden - obwohl er keinen besonders guten Anwendungsfall zitiert.

Die CPython-Core-Entwickler sind diskutieren über die Abwertung seiner Zulage. Hier ist ein relevanter Beitrag aus der Mailingliste:

Am 30. Januar 2017 um 19:05 schrieb Brett Cannon:

Auf Sun, 29 Jan 2017 um 16:39 schrieb Craig Rodrigues:

Ich bin mit beiden Ansatz einverstanden. Dinge so belassen, wie sie in Python 3 sind       ist nicht gut, IMHO.

Meine Stimme ist ein SyntaxError, weil Sie nicht bekommen, was Sie erwarten     die Syntax.

Ich stimme zu, dass dies ein vernünftiger Ort für uns ist, wie jeder Code   Sich auf das aktuelle Verhalten zu verlassen ist wirklich zu schlau um es zu sein   wartbar.

In Bezug darauf, werden wir wahrscheinlich wollen:

  • SyntaxWarning oder DeprecationWarning in 3.7
  • Py3k-Warnung in 2.7.x
  • SyntaxError in 3.8

Prost, Nick.

- Nick Coghlan | ncoghlan bei gmail.com | Brisbane, Australien

Außerdem gibt es ein ausstehendes Problem (10544) was in die Richtung zu zeigen scheint noch nie eine gute Idee (PyPy, eine in Python geschriebene Python-Implementierung, wirft bereits Syntaxwarnungen auf.)

Unterm Strich, bis die Entwickler von CPython uns etwas anderes sagen: Nicht setzen yield in einem Generator Ausdruck oder Verständnis.

Das return Aussage in einem Generator

Im Python 2:

In einer Generatorfunktion, die return Anweisung darf keine enthalten expression_list. In diesem Zusammenhang, eine bloße return zeigt an, dass der Generator fertig ist und verursachen wird StopIteration aufgezogen werden.

Ein expression_list ist im Prinzip eine beliebige Anzahl von Ausdrücken, die durch Kommata getrennt sind - im Wesentlichen können Sie in Python 2 den Generator mit stoppen return, aber Sie können keinen Wert zurückgeben.

Im Python 3:

In einer Generatorfunktion, die return Anweisung zeigt an, dass der Generator fertig ist und verursachen wird StopIteration aufgezogen werden. Der zurückgegebene Wert (falls vorhanden) wird als Argument zum Konstruieren verwendet StopIteration und wird zum StopIteration.valueAttribut.

Fußnoten

  1. Auf die Sprachen CLU, Sather und Icon wurde im Angebot verwiesen um das Konzept der Generatoren in Python einzuführen. Die allgemeine Idee ist dass eine Funktion den internen Zustand aufrechterhalten und intermediär liefern kann Datenpunkte auf Anfrage durch den Benutzer. Dies versprach zu sein überlegen in der Leistung zu anderen Ansätzen, einschließlich Python-Threading, die auf einigen Systemen nicht verfügbar ist.

  2.  Dies bedeutet zum Beispiel, dass xrange Objekte (range in Python 3) sind nicht Iterators, obwohl sie iterierbar sind, weil sie wiederverwendet werden können. Wie Listen, ihre __iter__ Methoden geben Iterator-Objekte zurück.

  3. yield wurde ursprünglich als eine Aussage eingeführt, was bedeutet, dass es könnte nur am Anfang einer Zeile in einem Codeblock erscheinen. Jetzt yield erstellt einen Yield-Ausdruck. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Diese Änderung war vorgeschlagen um es einem Benutzer zu ermöglichen, Daten genauso in den Generator zu senden man könnte es bekommen. Um Daten zu senden, muss man sie etwas zuweisen können, und Dafür wird eine Aussage einfach nicht funktionieren.


253
2018-06-25 06:11



yield ist einfach so return - es gibt zurück, was immer du ihm erzählst (als Generator). Der Unterschied besteht darin, dass beim nächsten Aufruf des Generators die Ausführung ab dem letzten Aufruf des Generators beginnt yield Erklärung. Im Gegensatz zu Rückkehr, der Stapelrahmen wird nicht bereinigt, wenn eine Ausbeute auftritt, jedoch wird die Steuerung an den Aufrufer zurückübertragen, so dass ihr Zustand beim nächsten Mal wieder hergestellt wird.

Im Falle Ihres Codes die Funktion get_child_candidates verhält sich wie ein Iterator, sodass Sie beim Hinzufügen der Liste jeweils ein Element zur neuen Liste hinzufügen.

list.extend ruft einen Iterator auf, bis er erschöpft ist. Im Falle des von Ihnen geposteten Codebeispiels wäre es viel einfacher, einfach ein Tupel zurückzugeben und dieses an die Liste anzuhängen.


230
2017-10-23 22:24



Es gibt noch eine weitere Sache, die erwähnt werden muss: Eine Funktion, die Erträge liefert, muss nicht wirklich enden. Ich habe Code wie folgt geschrieben:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Dann kann ich es in anderem Code wie folgt verwenden:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Es hilft wirklich dabei, einige Probleme zu vereinfachen, und erleichtert die Arbeit mit einigen Dingen.


182
2017-10-24 08:44



Für diejenigen, die ein minimales Arbeitsbeispiel bevorzugen, meditieren Sie über dieses interaktive Python Session:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed

155
2018-01-18 17:25



Ertrag gibt Ihnen einen Generator.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Wie Sie sehen können, hält foo im ersten Fall die gesamte Liste gleichzeitig im Speicher. Es ist keine große Sache für eine Liste mit 5 Elementen, aber was ist, wenn Sie eine Liste von 5 Millionen wollen? Dies ist nicht nur ein großer Speicherfresser, es kostet auch viel Zeit, um zu dem Zeitpunkt zu bauen, zu dem die Funktion aufgerufen wird. Im zweiten Fall gibt dir bar einfach einen Generator. Ein Generator ist ein iterabler Wert, dh Sie können ihn in einer for-Schleife usw. verwenden, aber auf jeden Wert kann nur einmal zugegriffen werden. Alle Werte werden nicht gleichzeitig im Speicher abgelegt. Das Generator-Objekt "erinnert sich" an die Stelle, an der es sich beim letzten Aufruf befand. Wenn Sie also einen iterablen Wert von 50 Milliarden verwenden, müssen Sie nicht auf 50 Milliarden zählen sofort und speichern Sie die 50 Milliarden Zahlen durch zu zählen. Auch dies ist ein ziemlich konstruiertes Beispiel, wahrscheinlich würden Sie Itirtools verwenden, wenn Sie wirklich zu 50 Milliarden zählen wollten. :)

Dies ist der einfachste Anwendungsfall von Generatoren. Wie Sie bereits gesagt haben, kann es verwendet werden, um effiziente Permutationen zu schreiben, wobei die Ausbeute verwendet wird, um die Dinge durch den Aufrufstapel zu schieben, anstatt eine Art Stapelvariable zu verwenden. Generatoren können auch für spezielle Baumdurchquerungen und alle möglichen anderen Dinge verwendet werden.


133
2018-01-16 06:42



Es gibt einen Generator zurück. Ich bin nicht besonders vertraut mit Python, aber ich glaube, es ist die gleiche Sache wie C # 's Iterator-Blöcke wenn du mit denen vertraut bist.

Dort ist ein IBM Artikel was erklärt es ziemlich gut (für Python) soweit ich sehen kann.

Die Schlüsselidee ist, dass der Compiler / Interpreter / was auch immer trickst, so dass der Aufrufer next () für den Aufrufer fortfahren kann und Werte zurückgibt - als ob die Generatormethode angehalten wurde. Nun kann man natürlich eine Methode nicht wirklich "pausieren", also baut der Compiler eine Zustandsmaschine, damit Sie sich merken können, wo Sie sich gerade befinden und wie die lokalen Variablen aussehen. Dies ist viel einfacher als das Schreiben eines Iterators selbst.


125
2017-10-23 22:26