Vraag Wat doet het "opbrengst" -woord?


Wat is het gebruik van de yield trefwoord in Python? Wat doet het?

Ik probeer bijvoorbeeld deze code te begrijpen1:

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  

En dit is de beller:

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

Wat gebeurt er met de methode _get_child_candidates wordt genoemd? Wordt een lijst geretourneerd? Een enkel element? Wordt het opnieuw genoemd? Wanneer stoppen de volgende oproepen?


1. De code komt van Jochen Schulz (jrschulz), die een geweldige Python-bibliotheek heeft gemaakt voor metrische spaties. Dit is de link naar de volledige bron: Module mspace.


8311
2017-10-23 22:21


oorsprong


antwoorden:


Om te begrijpen wat yield moet je begrijpen wat generatoren zijn. En voordat er generatoren komen iterables.

Iterables

Wanneer u een lijst maakt, kunt u de items ervan één voor één lezen. Het één voor één lezen van de items heet iteratie:

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

mylist is een iterable. Wanneer u een lijstbegrip gebruikt, maakt u een lijst en dus een iterabele:

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

Alles wat u kunt gebruiken "for... in..."aan is een iterabele; lists, strings, bestanden ...

Deze iterables zijn handig omdat je ze zo vaak kunt lezen als je wilt, maar je slaat alle waarden op in het geheugen en dit is niet altijd wat je wilt als je veel waarden hebt.

generatoren

Generators zijn iterators, een soort van iterable je kunt maar één keer herhalen. Generatoren slaan niet alle waarden op in het geheugen, ze genereren de waarden on the fly:

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

Het is hetzelfde, behalve dat je hebt gebruikt () in plaats van []. Maar jij kan niet uitvoeren for i in mygenerator een tweede keer omdat generatoren maar één keer kunnen worden gebruikt: ze berekenen 0, vergeten ze vervolgens en berekenen 1 en eindigen 4, één voor één.

Opbrengst

yield is een sleutelwoord dat wordt gebruikt als return, behalve dat de functie een generator retourneert.

>>> 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 is het een nutteloos voorbeeld, maar het is handig als je weet dat je functie een enorme reeks waarden zal teruggeven die je maar één keer hoeft te lezen.

Onder de knie krijgen yield, je moet dat begrijpen wanneer u de functie aanroept, wordt de code die u in de hoofdtekst van de functie hebt geschreven niet uitgevoerd. De functie retourneert alleen het generatorobject, dit is een beetje lastig :-)

Vervolgens wordt uw code telkens uitgevoerd wanneer de for maakt gebruik van de generator.

Nu het moeilijkste:

De eerste keer dat de for roept het generatorobject aan dat is gecreëerd met uw functie, het zal de code vanaf het begin in uw functie uitvoeren totdat deze wordt geraakt yield, dan zal het de eerste waarde van de lus retourneren. Vervolgens zal elke andere oproep de lus uitvoeren die u in de functie nog een keer hebt geschreven en de volgende waarde retourneren, totdat er geen waarde meer is om terug te keren.

De generator wordt als leeg beschouwd zodra de functie wordt uitgevoerd, maar niet wordt geraakt yield meer. Het kan zijn omdat de lus beëindigd is, of omdat je niet voldoet aan een "if/else" meer.


Uw code uitgelegd

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

beller:

# 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

Deze code bevat verschillende slimme delen:

  • De lus wordt herhaald in een lijst, maar de lijst wordt uitgevouwen terwijl de lus wordt herhaald :-) Het is een beknopte manier om al deze geneste gegevens te doorlopen, zelfs als het een beetje gevaarlijk is omdat je een oneindige lus kunt krijgen. In dit geval, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) haalt alle waarden van de generator uit, maar while blijft nieuwe generatorobjecten maken die andere waarden dan de vorige genereren, omdat deze niet op hetzelfde knooppunt worden toegepast.

  • De extend() methode is een lijstobjectmethode die een iterabele waarde verwacht en de bijbehorende waarden toevoegt aan de lijst.

Meestal geven we er een lijst aan door:

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

Maar in je code krijgt het een generator, wat goed is omdat:

  1. U hoeft de waarden niet twee keer te lezen.
  2. Je hebt misschien veel kinderen en je wilt niet dat ze allemaal in het geheugen worden opgeslagen.

En het werkt omdat Python er niet om geeft of het argument van een methode een lijst is of niet. Python verwacht iterables, dus het werkt met strings, lijsten, tuples en generators! Dit wordt eendtypering genoemd en is een van de redenen waarom Python zo cool is. Maar dit is een ander verhaal, voor een andere vraag ...

Je kunt hier stoppen of een klein stukje lezen om een ​​geavanceerd gebruik van een generator te zien:

Een generatoruitputting regelen

>>> 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
...

Notitie: Gebruik voor Python 3print(corner_street_atm.__next__()) of print(next(corner_street_atm))

Het kan handig zijn voor verschillende dingen, zoals het controleren van de toegang tot een bron.

Itertools, je beste vriend

De itertools-module bevat speciale functies om iterables te manipuleren. Ooit een generator willen dupliceren? Twee generatoren koppelen? Groepswaarden in een geneste lijst met een one-liner? Map / Zip zonder een nieuwe lijst te maken?

Dan gewoon import itertools.

Een voorbeeld? Laten we de mogelijke aankomstvolgordes bekijken voor een race met vier paarden:

>>> 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)]

De innerlijke mechanismen van iteratie begrijpen

Iteratie is een proces dat iterables impliceert (implementatie van de __iter__() methode) en iterators (implementatie van de __next__() methode). Iterables zijn alle objecten waar u een iterator van kunt krijgen. Iterators zijn objecten waarmee u iterables kunt herhalen.

Hierover gaat meer in dit artikel hoe for loops werken.


12173
2017-10-23 22:48



Snelkoppeling naar Grokking  yield

Wanneer je een functie ziet met yield en gebruik deze eenvoudige truc om te begrijpen wat er gaat gebeuren:

  1. Voeg een regel in result = [] aan het begin van de functie.
  2. Vervang elk yield expr met result.append(expr).
  3. Voeg een regel in return result onderaan de functie.
  4. Yay - niet meer yield statements! Lees en bereken code.
  5. Vergelijk functie met originele definitie.

Deze truc geeft je misschien een idee van de logica achter de functie, maar wat er feitelijk mee gebeurt yield is significant anders dan wat er gebeurt in de op lijst gebaseerde benadering. In veel gevallen zal de rendementsbenadering veel meer geheugenefficiënt en sneller zijn. In andere gevallen zorgt deze truc ervoor dat je vastloopt in een oneindige lus, ook al werkt de oorspronkelijke functie prima. Lees verder voor meer informatie ...

Verwar uw Iterables, Iterators en Generators niet met elkaar

Eerst de iterator-protocol - wanneer je schrijft

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

Python voert de volgende twee stappen uit:

  1. Krijgt een iterator voor mylist:

    telefoontje iter(mylist) -> dit retourneert een object met een next() methode (of __next__() in Python 3).

    [Dit is de stap die de meeste mensen vergeten te vertellen]

  2. Gebruikt de iterator om items over te lussen:

    Blijf bellen next() methode op de iterator terug van stap 1. De retourwaarde van next() is toegewezen aan x en het luslichaam wordt uitgevoerd. Als een uitzondering StopIteration wordt van binnenuit verhoogd next(), het betekent dat er geen waarden meer in de iterator zitten en dat de lus wordt afgesloten.

De waarheid is dat Python de bovenstaande twee stappen uitvoert op elk gewenst moment loop over de inhoud van een object - dus het kan een for-lus zijn, maar het kan ook een code zijn otherlist.extend(mylist) (waar otherlist is een Python-lijst).

Hier mylist is een iterable omdat het het iterator-protocol implementeert. In een door de gebruiker gedefinieerde klasse kunt u de __iter__() methode om instanties van uw klasse iterabel te maken. Deze methode zou een iterator. Een iterator is een object met een next() methode. Het is mogelijk om beide te implementeren __iter__() en next() in dezelfde klas, en hebben __iter__() terugkeer self. Dit werkt in eenvoudige gevallen, maar niet wanneer u twee iterators tegelijkertijd over hetzelfde object wilt laten lussen.

Dus dat is het iterator-protocol, veel objecten implementeren dit protocol:

  1. Ingebouwde lijsten, woordenboeken, tuples, sets, bestanden.
  2. Door de gebruiker gedefinieerde klassen die worden geïmplementeerd __iter__().
  3. Generators.

Merk op dat a for loop weet niet met wat voor soort object het te maken heeft - het volgt gewoon het iterator-protocol en is blij om item na item te krijgen als het roept next(). Ingebouwde lijsten retourneren hun items één voor één, woordenboeken retourneren de sleutels stuk voor stuk, bestanden retourneren de lijnen één voor één, enzovoort. En generatoren komen terug ... nou dat is waar yield komt binnen:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

In plaats van yield verklaringen, als u er drie had return verklaringen in f123() alleen de eerste zou worden uitgevoerd en de functie zou afsluiten. Maar f123() is geen gewone functie. Wanneer f123() wordt genoemd doet niet retourneer een van de waarden in de rendementsoverzichten! Het geeft een generatorobject terug. Ook wordt de functie niet echt afgesloten - deze gaat in een onderbroken toestand. Wanneer de for lus probeert het generatorobject te omlopen, de functie wordt hervat vanuit de uitgestelde status op de eerstvolgende regel na de yield het eerder terugkeerde van, voert de volgende regel code uit, in dit geval een yield statement en retourneert dat als het volgende item. Dit gebeurt totdat de functie wordt afgesloten, waarna de generator omhoog gaat StopIterationen de lus wordt afgesloten.

Het generatorobject is dus een beetje zoals een adapter - aan het ene uiteinde vertoont het het iteratorprotocol door het bloot te stellen __iter__() en next() methoden om de for loop gelukkig. Aan het andere uiteinde echter, wordt de functie net genoeg uitgevoerd om de volgende waarde eruit te halen en wordt deze teruggezet in de onderbroken modus.

Waarom generatoren gebruiken?

Meestal kun je code schrijven die geen generators gebruikt, maar dezelfde logica implementeert. Een optie is om de 'trick' van de tijdelijke lijst te gebruiken die ik eerder heb genoemd. Dat zal niet in alle gevallen werken, bijvoorbeeld voor als je oneindige loops hebt, of het kan een inefficiënt geheugengebruik maken als je een erg lange lijst hebt. De andere benadering is om een ​​nieuwe iterabele klasse te implementeren SomethingIter dat bewaart de status van de leden en voert de volgende logische stap uit next() (of __next__() in Python 3) methode. Afhankelijk van de logica, de code in de next() methode kan er heel complex uitzien en vatbaar zijn voor bugs. Hier bieden generatoren een schone en eenvoudige oplossing.


1635
2017-10-25 21:22



Zie het als volgt:

Een iterator is slechts een mooie klinkende term voor een object met een next () -methode. Dus een rendementsfunctie wordt zoiets als dit:

Originele versie:

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

for i in some_function():
    print i

Dit is in principe wat de Python-interpreter doet met de bovenstaande code:

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

Voor meer inzicht in wat er achter de schermen gebeurt, de for loop kan hier naartoe worden herschreven:

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

Heeft dat meer zin of verwar je meer? :)

Ik moet dit opmerken is een oversimplificatie voor illustratieve doeleinden. :)


396
2017-10-23 22:28



De yield sleutelwoord wordt teruggebracht tot twee simpele feiten:

  1. Als de compiler de yield trefwoord overal binnen een functie keert die functie niet meer terug via de return uitspraak. In plaats daarvan, het per direct geeft a terug lui object "in behandeling" een generator genoemd
  2. Een generator is iterabel. Wat is een iterable? Het is zoiets als een list of set of range of dict-view, met een ingebouwd protocol voor het bezoeken van elk element in een bepaalde volgorde.

In een notendop: een generator is een luie, stapsgewijze lijst, en yield Met opdrachten kunt u de functie notatie gebruiken om de lijstwaarden te programmeren de generator moet stapsgewijs uitspugen.

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]]

Voorbeeld

Laten we een functie definiëren makeRange dat is net als Python's range. Roeping makeRange(n) RETOUR EEN GENERATOR:

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>

Om de generator te dwingen onmiddellijk de verwachte waarden ervan in te leveren, kunt u deze doorgeven list() (net zoals je elke iterable zou kunnen zijn):

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

Voorbeeld vergelijken met "gewoon een lijst retourneren"

Het bovenstaande voorbeeld kan worden beschouwd als het maken van een lijst die u toevoegt aan en retourneert:

# 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]

Er is echter een groot verschil; zie de laatste sectie.


Hoe u generators kunt gebruiken

Een iterabele is het laatste deel van een lijstbegrip, en alle generators zijn iterabel, dus ze worden vaak als volgt gebruikt:

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

Om een ​​beter gevoel voor generatoren te krijgen, kun je spelen met de itertools module (gebruik zeker chain.from_iterable liever dan chain wanneer gerechtvaardigd). U kunt bijvoorbeeld zelfs generatoren gebruiken om oneindig lange lui lijsten te implementeren zoals itertools.count(). Je zou je eigen kunnen implementeren def enumerate(iterable): zip(count(), iterable), of doe dat als alternatief met de yieldsleutelwoord in een while-lus.

Let op: generatoren kunnen in feite voor veel meer dingen worden gebruikt, zoals coroutines implementeren of niet-deterministische programmering of andere elegante dingen. Het 'luie lijsten'-gezichtspunt dat ik hier presenteer, is echter het meest voorkomende gebruik dat u zult vinden.


Achter de schermen

Dit is hoe het "Python-iteratieprotocol" werkt. Dat is, wat er gaande is als je dat doet list(makeRange(5)). Dit is wat ik eerder beschreef als een "luie, incrementele lijst".

>>> 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

De ingebouwde functie next() roept gewoon de objecten .next() functie, die deel uitmaakt van het "iteratieprotocol" en wordt aangetroffen op alle iterators. U kunt de. Handmatig gebruiken next() functie (en andere delen van het iteratieprotocol) om mooie dingen te implementeren, meestal ten koste van de leesbaarheid, dus probeer dat te voorkomen ...


kleinigheden

Normaal gesproken geven de meeste mensen niet om de volgende onderscheidingen en willen ze waarschijnlijk hier stoppen met lezen.

In Python-spreken, een iterable is een object dat "het concept van een for-lus begrijpt" zoals een lijst [1,2,3], en een iterator is een specifiek exemplaar van de gevraagde for-loop-achtige [1,2,3].__iter__(). EEN generator is exact hetzelfde als elke iterator, behalve de manier waarop het is geschreven (met functiesyntaxis).

Wanneer u een iterator in een lijst opvraagt, wordt er een nieuwe iterator gemaakt. Wanneer u echter een iterator van een iterator aanvraagt ​​(wat u zelden zou doen), krijgt u gewoon een kopie van zichzelf.

Dus in het onwaarschijnlijke geval dat het u niet lukt om zoiets te doen ...

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

... onthoud dan dat een generator een is iterator; dat wil zeggen, het is eenmalig gebruik. Als je het opnieuw wilt gebruiken, moet je bellen myRange(...) nog een keer. Als u het resultaat twee keer wilt gebruiken, converteert u het resultaat naar een lijst en slaat u het op in een variabele x = list(myRange(5)). Degenen die absoluut een generator moeten klonen (bijvoorbeeld, die angstaanjagend hackachtige metaprogrammering aan het doen zijn) kunnen gebruiken itertools.tee als het absoluut noodzakelijk is, sinds de kopieerbare iterator Python FUT voorstel voor normen is uitgesteld.


346
2018-06-19 06:33



Wat doet de yield keyword do in Python?

Antwoordoverzicht / samenvatting

  • Een functie met yield, wanneer gebeld, geeft a terug Generator.
  • Generators zijn iterators omdat ze het iterator-protocol, dus je kunt ze herhalen.
  • Een generator kan ook zijn verzonden informatie, waardoor het conceptueel een coroutine.
  • In Python 3 kan dat delegeren van de ene generator naar de andere in beide richtingen met yield from.
  • (Bijlage bekritiseert een paar antwoorden, inclusief de bovenste, en bespreekt het gebruik van return in een generator.)

generatoren:

yield is alleen legaal binnen een functiedefinitie, en de opname van yield in een functiedefinitie maakt het een generator terug.

Het idee voor generators komt uit andere talen (zie voetnoot 1) met verschillende implementaties. In Python's Generators is de uitvoering van de code bevroren op het punt van de opbrengst. Wanneer de generator wordt opgeroepen (methoden worden hieronder besproken) wordt de uitvoering hervat en bevriest deze bij de volgende opbrengst.

yield biedt een gemakkelijke manier om het implementeren van het iterator-protocol, gedefinieerd door de volgende twee methoden: __iter__ en next (Python 2) of __next__ (Python 3). Beide methoden maak een object een iterator die je zou kunnen typen - controleer met de Iterator Abstracte basis Klasse uit de collections module.

>>> 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.

Het generatortype is een subtype van iterator:

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

En indien nodig, kunnen we als volgt typen:

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

Een kenmerk van een Iterator  is dat ooit uitgeput, u kunt het niet opnieuw gebruiken of opnieuw instellen:

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

U moet een nieuwe maken als u de functionaliteit opnieuw wilt gebruiken (zie voetnoot 2):

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

Men kan gegevens programmatisch opleveren, bijvoorbeeld:

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

De bovenstaande eenvoudige generator is ook gelijk aan de onderstaande - vanaf Python 3.3 (en niet beschikbaar in Python 2), kunt u gebruiken yield from:

def func(an_iterable):
    yield from an_iterable

Echter, yield from maakt ook delegatie naar subgenerators mogelijk, wat zal worden uitgelegd in de volgende sectie over coöperatieve delegatie met subcoroutines.

coroutines:

yield vormt een uitdrukking waarmee gegevens naar de generator kunnen worden verzonden (zie voetnoot 3)

Hier is een voorbeeld, let op de received variabele, die verwijst naar de gegevens die naar de generator worden verzonden:

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)

Eerst moeten we de generator in de wachtrij plaatsen met de ingebouwde functie, next. Het zal bel de juiste next of __next__ methode, afhankelijk van de versie van Python die u gebruikt:

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

En nu kunnen we gegevens naar de generator sturen. (Bezig met verzenden None is hetzelfde als bellen next.):

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

Coöperatieve Delegatie naar Sub-Coroutine met yield from

Onthoud dat nu yield from is beschikbaar in Python 3. Hiermee kunnen we delegeren coroutines naar een 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()

En nu kunnen we functionaliteit delegeren aan een subgenerator en deze kan worden gebruikt door een generator, net als hierboven:

>>> 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

U kunt meer lezen over de precieze semantiek van yield from in PEP 380.

Andere methoden: sluiten en gooien

De close methode verhoogt GeneratorExit op het punt de functie uitvoering was bevroren. Dit wordt ook aangeroepen door __del__ dus jij kan elke opschooncode invoeren waar u de GeneratorExit:

>>> my_account.close()

Je kunt ook een uitzondering weggooien die in de generator kan worden afgehandeld of doorgegeven aan de gebruiker:

>>> 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

Conclusie

Ik geloof dat ik alle aspecten van de volgende vraag heb behandeld:

Wat doet de yield keyword do in Python?

Het blijkt dat yield doet veel. Ik weet zeker dat ik er nog meer aan zou kunnen toevoegen gedegen voorbeelden hiervan. Als je meer wilt of een beetje opbouwende kritiek hebt, laat het me weten door te reageren hieronder.


Bijlage:

Kritiek op het Top / Aanvaarde Antwoord **

  • Het is verward over wat een iterable, gewoon een lijst gebruiken als een voorbeeld. Zie mijn referenties hierboven, maar kort samengevat: een iterabele heeft een __iter__ methode die een retourneert iterator. Een iterator verschaft een .next (Python 2 of .__next__ (Python 3) methode, die impliciet wordt aangeroepen door forlussen totdat deze omhoog gaat StopIterationen als dat eenmaal het geval is, blijft het dit doen.
  • Vervolgens wordt een generatorexpressie gebruikt om te beschrijven wat een generator is. Omdat een generator eenvoudigweg een handige manier is om een ​​te maken iterator, het verwart alleen de kwestie, en we zijn er nog steeds niet aan toe yield een deel.
  • In Een generatoruitputting regelen hij noemt het .next methode, in plaats daarvan zou hij de ingebouwde functie moeten gebruiken, next. Het zou een geschikte laag van indirectie zijn, omdat zijn code niet werkt in Python 3.
  • Itertools? Dit was niet relevant voor wat yield helemaal niet.
  • Geen discussie over de methoden die yield biedt samen met de nieuwe functionaliteit yield from in Python 3. Het bovenste / geaccepteerde antwoord is een zeer onvolledig antwoord.

Kritiek op antwoord suggereert yield in een generatoruitdrukking of -begrip.

De grammatica staat momenteel elke uitdrukking in een lijstbegrip toe.

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

Omdat opbrengst een uitdrukking is, is het door sommigen aangeprezen als interessant om het te gebruiken in begrippen of uitdrukkingen van de generator, ondanks het feit dat er geen bijzonder goede gebruikscasus wordt genoemd.

De kernontwikkelaars van CPython zijn bespreken van het afwaarderen van zijn uitkering. Hier is een relevante post van de mailinglijst:

Op 30 januari 2017 om 19:05 schreef Brett Cannon:

Op zondag 29 januari 2017 om 16:39 schreef Craig Rodrigues:

Ik ben OK met beide benaderingen. De dingen laten zoals ze zijn in Python 3       heeft geen zin, IMHO.

Mijn stem is het een SyntaxError omdat je niet krijgt wat je verwacht     de syntaxis.

Ik ben het ermee eens dat dit een verstandige plek is om als elke code te eindigen   een beroep doen op het huidige gedrag is echt te slim om te zijn   onderhoudbaar.

Wat betreft het bereiken van ons doel:

  • Syntaxiswaarschuwing of kwijtschelding Waarschuwing in 3.7
  • Py3k-waarschuwing in 2.7.x
  • SyntaxError in 3.8

Proost, Nick.

- Nick Coghlan | ncoghlan op gmail.com | Brisbane, Australië

Verder is er een openstaande uitgave (10544) dat lijkt in de richting van dit te wijzen nooit een goed idee zijn (PyPy, een Python-implementatie geschreven in Python, verhoogt al de syntaxwaarschuwingen.)

Kort gezegd, totdat de ontwikkelaars van CPython ons anders vertellen: Zet niet yield in een generatoruitdrukking of -begrip.

De return verklaring in een generator

In Python 2:

In een generatorfunctie, de return verklaring is niet toegestaan ​​om een expression_list. In die context een kaal return geeft aan dat de generator klaar is en zal veroorzaken StopIteration opgevoed worden.

Een expression_list is in principe een willekeurig aantal expressies gescheiden door komma's - in feite kun je in Python 2 de generator stoppen met return, maar u kunt geen waarde retourneren.

In Python 3:

In een generatorfunctie, de return verklaring geeft aan dat de generator klaar is en zal veroorzaken StopIteration opgevoed worden. De geretourneerde waarde (indien aanwezig) wordt gebruikt als een argument om te construeren StopIteration en wordt de StopIteration.valueattribuut.

voetnoten

  1. De talen CLU, Sather en Icon zijn in het voorstel vermeld om het concept van generators aan Python te introduceren. Het algemene idee is dat een functie de interne status kan behouden en tussentijden kan opleveren gegevenspunten op verzoek van de gebruiker. Dit beloofde te zijn superieur in prestaties naar andere benaderingen, inclusief Python threading, wat zelfs niet beschikbaar is op sommige systemen.

  2.  Dit betekent bijvoorbeeld dat xrange voorwerpen (range in Python 3) niet Iterators, ook al zijn ze iterabel, omdat ze kunnen worden hergebruikt. Zoals lijsten, hun __iter__ methoden retourneren iterator-objecten.

  3. yield werd oorspronkelijk geïntroduceerd als een verklaring, wat betekent dat het kan alleen aan het begin van een regel in een codeblok verschijnen. Nu yield maakt een opbrengstuitdrukking. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Deze verandering was voorgestelde om een ​​gebruiker toe te staan ​​om gegevens naar de generator te sturen, net zoals iemand zou het kunnen ontvangen. Om gegevens te kunnen verzenden, moet u het aan iets kunnen toewijzen, en daarvoor zal een verklaring gewoon niet werken.


253
2018-06-25 06:11



yield is net zoals return - het geeft terug wat je het vertelt (als een generator). Het verschil is dat de volgende keer dat u de generator oproept, de uitvoering start vanaf de laatste oproep naar de yield uitspraak. In tegenstelling tot terugkeer, het stapelframe wordt niet opgeruimd als er een opbrengst optreedt, maar de besturing wordt teruggestuurd naar de beller, dus de status wordt hervat bij de volgende keer dat de functie wordt gebruikt.

In het geval van uw code, de functie get_child_candidates gedraagt ​​zich als een iterator, zodat wanneer u uw lijst uitbreidt, het één element tegelijk toevoegt aan de nieuwe lijst.

list.extend roept een iterator totdat deze uitgeput is. In het geval van het codevoorbeeld dat u hebt gepost, zou het veel duidelijker zijn om alleen een tuple terug te sturen en die aan de lijst toe te voegen.


230
2017-10-23 22:24



Er is nog een extra ding om op te noemen: een functie die oplevert hoeft eigenlijk niet te eindigen. Ik heb code als deze geschreven:

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

Dan kan ik het in een andere code als deze gebruiken:

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

Het helpt echt om sommige problemen te vereenvoudigen en maakt sommige dingen gemakkelijker om mee te werken.


182
2017-10-24 08:44



Voor degenen die de voorkeur geven aan een minimaal werkend voorbeeld, mediteer op deze interactieve Python sessie:

>>> 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



Opbrengst geeft je een 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

Zoals je ziet, houdt foo in het eerste geval de hele lijst in het geheugen in één keer vast. Het is geen big deal voor een lijst met 5 elementen, maar wat als je een lijst van 5 miljoen wilt? Dit is niet alleen een enorme geheugeneter, het kost ook veel tijd om te bouwen op het moment dat de functie wordt genoemd. In het tweede geval geeft de balk u gewoon een generator. Een generator is een iterabele - wat betekent dat u het in een for-lus kunt gebruiken, enz., Maar elke waarde kan slechts eenmaal worden gebruikt. Alle waarden worden ook niet tegelijkertijd in het geheugen opgeslagen; het generatorobject "onthoudt" waar het zich in de looping bevond de laatste keer dat je het noemde - op deze manier hoef je niet tot 50 miljard te tellen als je een iterabele gebruikt om (laten we zeggen) tot 50 miljard te tellen tegelijk en bewaar de 50 miljard nummers om door te rekenen. Nogmaals, dit is een vrij gekunsteld voorbeeld, je zou itertools waarschijnlijk gebruiken als je echt tot 50 miljard wilt tellen. :)

Dit is het meest eenvoudige geval van generatoren. Zoals je al zei, kan het worden gebruikt om efficiënte permutaties te schrijven, met behulp van opbrengst om dingen omhoog te duwen door de call stack in plaats van een soort stackvariabele te gebruiken. Generatoren kunnen ook worden gebruikt voor gespecialiseerde boombewegingen en allerlei andere dingen.


133
2018-01-16 06:42