Hva er bruken av nøkkelordet yield
i Python? Hva gjør det?
Jeg prøver for eksempel å forstå denne koden1:
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
Og dette er innringeren:
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
Hva skjer når metoden _get_child_candidates
kalles?
Returneres en liste? Et enkelt element? Blir den kalt opp igjen? Når stopper de påfølgende anropene?
Tenk på det på denne måten:
En iterator er bare en fancy betegnelse for et objekt som har en next()
-metode. Så en yield-ed-funksjon ender opp med å være noe sånt som dette:
Opprinnelig versjon:
def some_function():
for i in xrange(4):
yield i
for i in some_function():
print i
Dette er i utgangspunktet hva Python-tolkeren gjør med koden ovenfor:
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
Hvis du vil ha mer innsikt i hva som skjer bak kulissene, kan for
-løkken skrives om til dette:
iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass
Gir det mer mening, eller blir du bare enda mer forvirret:)
Jeg bør merke meg at dette er en overforenkling for illustrasjonsformål :)
yield
er akkurat som return
- den returnerer det du ber den om (som en generator). Forskjellen er at neste gang du kaller generatoren, starter kjøringen fra det siste anropet til yield
-setningen. I motsetning til return, **renses ikke stabelrammen når en yield oppstår, men kontrollen overføres tilbake til innringeren, slik at tilstanden gjenopptas neste gang funksjonen kalles.
I koden din fungerer funksjonen get_child_candidates
som en iterator, slik at når du utvider listen, legger den til ett element om gangen i den nye listen.
list.extend
kaller en iterator til den er oppbrukt. I kodeeksemplet du la ut, ville det vært mye mer oversiktlig å bare returnere en tupel og legge den til listen.
Den returnerer en generator. Jeg er ikke spesielt kjent med Python, men jeg tror det er det samme som C#'s iteratorblokker hvis du er kjent med dem.
Hovedideen er at kompilatoren/fortolkeren/whatever gjør noen triks slik at innringeren kan fortsette å kalle next(), og den vil fortsette å returnere verdier - som om generatormetoden var satt på pause. Nå kan du selvsagt ikke "pause" en metode, så kompilatoren bygger en tilstandsmaskin for å huske hvor du befinner deg og hvordan de lokale variablene osv. ser ut. Dette er mye enklere enn å skrive en iterator selv.