Um zu verstehen, was yield
funktioniert, müssen Sie verstehen, was Generatoren sind. Und bevor Sie Generatoren verstehen können, müssen Sie iterables verstehen .
Iterables
Wenn Sie eine Liste erstellen, können Sie deren Elemente einzeln lesen. Das Lesen der Elemente nacheinander wird als Iteration bezeichnet:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
ist eine iterable . Wenn Sie ein Listenverständnis verwenden, erstellen Sie eine Liste und damit eine iterierbare:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Alles, wofür Sie " for... in...
" verwenden können, ist iterierbar. lists
, strings
, Dateien ...
Diese iterablen Dateien sind praktisch, da Sie sie beliebig oft lesen können, aber alle Werte im Speicher speichern. Dies ist nicht immer das, was Sie möchten, wenn Sie viele Werte haben.
Generatoren
Generatoren sind Iteratoren, eine Art Iterierbarkeit, die Sie nur einmal durchlaufen können . 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 genau das gleiche, außer dass Sie ()
anstelle von verwendet haben []
. ABER Sie können keinfor i in mygenerator
zweites Mal ausführen, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, vergessen es dann und berechnen 1 und beenden die Berechnung von 4 nacheinander.
Ausbeute
yield
ist ein Schlüsselwort, das wie folgt 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 Anzahl von Werten zurückgibt, die Sie nur einmal lesen müssen.
Um zu beherrschen yield
, müssen Sie verstehen, dass beim Aufrufen der Funktion der Code, den Sie in den Funktionskörper geschrieben haben, nicht ausgeführt wird. Die Funktion gibt nur das Generatorobjekt zurück, das ist etwas knifflig :-)
Dann wird Ihr Code bei jeder for
Verwendung des Generators dort fortgesetzt, wo er aufgehört hat.
Nun der schwierige Teil:
for
Wenn Sie das aus Ihrer Funktion erstellte Generatorobjekt zum ersten Mal aufrufen, wird der Code in Ihrer Funktion von Anfang an ausgeführt, bis er trifft yield
. Anschließend wird der erste Wert der Schleife zurückgegeben. Dann führt jeder nachfolgende Aufruf eine weitere Iteration der Schleife aus, die Sie in die Funktion geschrieben haben, und gibt den nächsten Wert zurück. Dies wird fortgesetzt, bis der Generator als leer betrachtet wird. Dies geschieht, wenn die Funktion ohne Treffer ausgeführt wird yield
. Das kann daran liegen, dass die Schleife beendet ist oder dass Sie eine nicht mehr erfüllen "if/else"
.
Ihr 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 the 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 the 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 candidate's 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 in einer Liste, aber die Liste wird erweitert, während die Schleife iteriert wird :-) Dies ist eine übersichtliche Methode, um alle diese verschachtelten Daten zu durchlaufen, auch wenn dies etwas gefährlich ist, da Sie möglicherweise eine Endlosschleife erhalten. In diesem Fall candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
erschöpfen Sie alle Werte des Generators, while
erstellen jedoch weiterhin neue Generatorobjekte, die andere Werte als die vorherigen erzeugen, da sie nicht auf denselben Knoten angewendet werden.
Die extend()
Methode ist eine Listenobjektmethode, die eine Iterierbarkeit erwartet und ihre Werte zur Liste hinzufügt.
Normalerweise übergeben wir ihm eine Liste:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Aber in Ihrem Code bekommt es einen Generator, was gut ist, weil:
- Sie müssen die Werte nicht zweimal lesen.
- Möglicherweise haben Sie viele Kinder und möchten nicht, dass 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 Zeichenfolgen, Listen, Tupeln und Generatoren funktioniert! Dies wird als Ententypisierung bezeichnet 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:
Steuerung einer Generatorerschöpfung
>>> 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 3 print(corner_street_atm.__next__())
oderprint(next(corner_street_atm))
Dies kann für verschiedene Zwecke nützlich sein, z. B. zum Steuern des Zugriffs auf eine Ressource.
Itertools, dein bester Freund
Das Modul itertools enthält spezielle Funktionen zum Bearbeiten von Iterables. Möchten Sie jemals einen Generator duplizieren? Kette zwei Generatoren? Werte in einer verschachtelten Liste mit einem Einzeiler gruppieren? Map / Zip
ohne eine andere Liste zu erstellen?
Dann einfach import itertools
.
Ein Beispiel? Sehen wir uns die möglichen Ankunftsreihenfolgen für ein Vierpferderennen an:
>>> 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 (Implementierung der __iter__()
Methode) und Iteratoren (Implementierung der __next__()
Methode) impliziert . Iterables sind alle Objekte, von denen Sie einen Iterator erhalten können. Iteratoren sind Objekte, mit denen Sie iterable iterieren können.
In diesem Artikel erfahren Sie mehr darüber, wie for
Schleifen funktionieren .
yield
ist nicht so magisch, wie diese Antwort nahelegt. Wenn Sie eine Funktion aufrufen, dieyield
irgendwo eine Anweisung enthält , erhalten Sie ein Generatorobjekt, aber es wird kein Code ausgeführt. Jedes Mal, wenn Sie ein Objekt aus dem Generator extrahieren, führt Python Code in der Funktion aus, bis eineyield
Anweisung vorliegt , hält das Objekt an und liefert es aus. Wenn Sie ein anderes Objekt extrahieren, wird Python direkt nach demyield
fortgesetzt und fortgesetzt, bis es ein anderes erreichtyield
(häufig dasselbe, aber eine Iteration später). Dies wird fortgesetzt, bis die Funktion am Ende abläuft. An diesem Punkt gilt der Generator als erschöpft.