Um zu verstehen, was yield
tut, müssen Sie verstehen, was Stromerzeuger sind. Und bevor Sie Generatoren verstehen können, müssen Sie verstehen Iterables .
Iterables
Wenn Sie eine Liste erstellen, können Sie deren Elemente nacheinander lesen. Das Lesen der Elemente nacheinander 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 eine Iterable:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Alles, was Sie verwenden können " for... in...
on" ist eine Iterable; lists
, strings
, Dateien...
Diese Iterables sind praktisch, weil man sie beliebig oft lesen kann, aber man speichert alle Werte im Speicher, und das ist nicht immer das, was man will, wenn man viele Werte hat.
Stromerzeuger
Generatoren sind Iteratoren, eine Art Iterable können Sie nur einmal durchlaufen . Die 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 dasselbe, außer dass Sie ()
anstelle von []
. ABER, Sie kann nicht durchführen for i in mygenerator
ein zweites Mal, da Generatoren nur einmal verwendet werden können: Sie berechnen 0, dann vergessen sie es und berechnen 1, und am Ende berechnen sie 4, einen nach dem anderen.
Ausbeute
yield
ist ein Schlüsselwort, das wie folgt verwendet wird return
mit dem Unterschied, dass die Funktion einen Generator zurückgibt.
>>> def create_generator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Hier handelt es sich um ein nutzloses Beispiel, aber es ist praktisch, wenn Sie wissen, dass Ihre Funktion eine große Menge von Werten zurückgeben wird, die Sie nur einmal lesen müssen.
Zu meistern yield
müssen Sie verstehen, dass Wenn Sie die Funktion aufrufen, wird der Code, den Sie in den Funktionsrumpf 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 dort fortgesetzt, wo er aufgehört hat for
verwendet den Generator.
Jetzt kommt der schwierige Teil:
Das erste Mal, dass die for
das Generatorobjekt aufruft, das aus Ihrer Funktion erzeugt wurde, wird der Code in Ihrer Funktion von Anfang an ausgeführt, bis er auf yield
zurück, dann wird der erste Wert der Schleife zurückgegeben. Bei jedem weiteren Aufruf wird eine weitere Iteration der Schleife, die Sie in die Funktion geschrieben haben, ausgeführt und der nächste Wert zurückgegeben. Dies wird so lange fortgesetzt, bis der Generator als leer angesehen wird, was dann der Fall ist, wenn die Funktion läuft, ohne auf yield
. Das kann daran liegen, dass die Schleife zu Ende ist, oder daran, dass Sie eine Anforderung 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 eine Liste, aber die Liste erweitert sich, während die Schleife iteriert wird. Dies ist eine übersichtliche Methode, um all diese verschachtelten Daten durchzugehen, auch wenn sie etwas gefährlich ist, da sie in einer Endlosschleife enden kann. In diesem Fall, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
alle Werte des Generators ausschöpfen, aber while
erzeugt immer wieder neue Generatorobjekte, die andere Werte als die vorherigen erzeugen, da sie nicht auf denselben Knoten angewendet werden.
-
Le site extend()
Methode ist eine Listenobjektmethode, die eine iterable erwartet und deren 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 wird es ein Generator, was gut ist, weil:
- Sie brauchen die Werte nicht zweimal zu lesen.
- Vielleicht haben Sie viele Kinder und möchten nicht, dass sie alle im Speicher abgelegt werden.
Und es funktioniert, weil es Python nicht interessiert, ob das Argument einer Methode eine Liste ist oder nicht. Python erwartet Iterables, also funktioniert es mit Strings, Listen, Tupeln und Generatoren! Das nennt man Duck Typing 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 aufhören oder ein wenig weiter lesen, um einen fortgeschrittenen Einsatz eines Generators zu sehen:
Kontrolle der Erschöpfung eines Generators
>>> 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
...
Anmerkung: Für Python 3, verwenden Sie print(corner_street_atm.__next__())
o print(next(corner_street_atm))
Sie kann für verschiedene Dinge nützlich sein, z. B. für die Kontrolle des Zugangs zu einer Ressource.
Itertools, Ihr bester Freund
Das Modul itertools enthält spezielle Funktionen zur Manipulation von Iterables. Möchten Sie jemals einen Generator duplizieren? Zwei Generatoren verketten? Werte in einer verschachtelten Liste mit einem Einzeiler gruppieren? Map / Zip
ohne eine weitere Liste zu erstellen?
Dann einfach import itertools
.
Ein Beispiel? Schauen wir uns die möglichen Ankunftsreihenfolgen für ein Vier-Pferde-Rennen 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)]
Verstehen der inneren Mechanismen der Iteration
Iteration ist ein Prozess, der Iterabilien beinhaltet (Implementierung der __iter__()
Methode) und Iteratoren (Implementierung der __next__()
Methode). Iterables sind alle Objekte, von denen man einen Iterator erhalten kann. Iteratoren sind Objekte, die eine Iteration von Iterables ermöglichen.
Mehr dazu gibt es in diesem Artikel über comment for
Schleifen arbeiten .