Wat levert de “opbrengst” zoekwoord doen?

Wat is het gebruik van het sleutelwoord yield 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 als de methode _get_child_candidates wordt aangeroepen?
Wordt er een lijst geretourneerd? Een enkel element? Wordt er weer gebeld? Wanneer stoppen volgende gesprekken?



1. Dit stukje code is geschreven door Jochen Schulz (jrschulz), die een geweldige Python-bibliotheek voor metrische ruimten heeft gemaakt. Dit is de link naar de volledige bron: [Module mspace][1].


Antwoord 1, autoriteit 100%

Om te begrijpen wat yield doet, moet je begrijpen wat generatoren zijn. En voordat u generatoren kunt begrijpen, moet u iterables begrijpen.

Iterables

Als je een lijst maakt, kun je de items een voor een lezen. Het één voor één lezen van de items wordt iteratie genoemd:

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

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

Alles wat je kunt gebruiken "for... in..." aan is een itereerbaar; lists, strings, bestanden…

Deze herhalingen 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 itereerbare 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 precies hetzelfde, behalve dat je () hebt gebruikt in plaats van []. MAAR, u kunt for i in mygenerator een tweede keer uitvoeren omdat generatoren maar één keer kunnen worden gebruikt: ze berekenen 0, vergeten het dan en berekenen 1, en eindigen met berekenen 4 , één voor één.

Opbrengst

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

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

Om yield onder de knie te krijgen, moet u begrijpen dat wanneer u de functie aanroept, de code die u in de hoofdtekst van de functie hebt geschreven, niet wordt uitgevoerd. De functie retourneert alleen de generator object, dit is een beetje lastig.

Vervolgens gaat je code verder waar het gebleven was, elke keer dat for de generator gebruikt.

Nu het moeilijkste:

De eerste keer dat de for het generatorobject aanroept dat is gemaakt met uw functie, wordt de code in uw functie vanaf het begin uitgevoerd totdat deze yield bereikt, waarna het’ ll retourneert de eerste waarde van de lus. Vervolgens voert elke volgende aanroep een nieuwe iteratie uit van de lus die u in de functie hebt geschreven en retourneert de volgende waarde. Dit gaat door totdat de generator als leeg wordt beschouwd, wat gebeurt wanneer de functie wordt uitgevoerd zonder op yield te drukken. Dat kan zijn omdat de loop tot een einde is gekomen, of omdat je niet langer voldoet aan een "if/else".


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

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

Deze code bevat verschillende slimme onderdelen:

  • De lus herhaalt zich op een lijst, maar de lijst wordt groter 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 putten candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) alle waarden van de generator uit, maar while blijft nieuwe generatorobjecten maken die andere waarden produceren dan de vorige omdat het niet op hetzelfde knooppunt wordt toegepast.

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

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. Je 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 het Python niet uitmaakt of het argument van een methode een lijst is of niet. Python verwacht iterables, dus het zal werken met strings, lijsten, tupels en generatoren! Dit wordt duck-typing 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:

Het beheersen van de uitputting van een generator

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

Opmerking: 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 aan een ketting? Waarden groeperen in een geneste lijst met een oneliner? Map / Zip zonder nog een lijst te maken?

Vervolgens import itertools.

Een voorbeeld? Laten we eens kijken naar de mogelijke aankomstvolgorde 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 waarvan je een iterator kunt krijgen. Iterators zijn objecten waarmee je iterables kunt herhalen.

Er staat meer over in dit artikel over hoe for loops werken.


Antwoord 2, autoriteit 14%

Snelkoppeling om yield

te begrijpen

Als je een functie ziet met yield-statements, pas dan deze eenvoudige truc toe om te begrijpen wat er zal gebeuren:

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

Deze truc geeft je misschien een idee van de logica achter de functie, maar wat er feitelijk gebeurt met yield is aanzienlijk anders dan wat er gebeurt in de op lijsten gebaseerde benadering. In veel gevallen zal de opbrengstbenadering ook veel efficiënter en sneller zijn. In andere gevallen zorgt deze truc ervoor dat je vast komt te zitten in een oneindige lus, ook al werkt de originele functie prima. Lees verder voor meer informatie…

Verwar je Iterables, Iterators en Generators niet

Eerst het iterator-protocol – wanneer u schrijft

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

Python voert de volgende twee stappen uit:

  1. Krijgt een iterator voor mylist:

    Bel 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 te herhalen:

    Blijf de methode next() aanroepen op de iterator die is geretourneerd uit stap 1. De geretourneerde waarde van next() wordt toegewezen aan x en het luslichaam wordt uitgevoerd. Als een uitzondering StopIteration wordt gegenereerd vanuit next(), betekent dit dat er geen waarden meer in de iterator zijn en dat de lus wordt verlaten.

De waarheid is dat Python de bovenstaande twee stappen uitvoert wanneer het de inhoud van een object wil loopen – dus het kan een for-lus zijn, maar het kan ook code zijn zoals otherlist.extend(mylist) (waarbij otherlist een Python-lijst is).

Hier is mylist een iterable omdat het het iteratorprotocol implementeert. In een door de gebruiker gedefinieerde klasse kunt u de methode __iter__() implementeren om instanties van uw klasse itereerbaar te maken. Deze methode zou een iterator moeten retourneren. Een iterator is een object met een next() methode. Het is mogelijk om zowel __iter__() als next() op dezelfde klasse te implementeren, en __iter__() self. Dit werkt voor eenvoudige gevallen, maar niet wanneer u wilt dat twee iterators tegelijkertijd over hetzelfde object lopen.

Dus dat is het iteratorprotocol, veel objecten implementeren dit protocol:

  1. Ingebouwde lijsten, woordenboeken, tupels, sets, bestanden.
  2. Door de gebruiker gedefinieerde klassen die __iter__() implementeren.
  3. Generatoren.

Merk op dat een for-lus niet weet met wat voor soort object het te maken heeft – het volgt gewoon het iteratorprotocol en is blij om item na item te krijgen terwijl het next(). Ingebouwde lijsten retourneren hun items één voor één, woordenboeken retourneren de sleutels één voor één, bestanden retourneren de regels één voor één, enz. En generatoren keren terug… nou ja dat is waar yield van pas komt:

def f123():
    yield 1
    yield 2
    yield 3
for item in f123():
    print item

In plaats van yield-instructies, als u drie return-instructies in f123() had, zou alleen de eerste worden uitgevoerd, en de functie zou Uitgang. Maar f123() is geen gewone functie. Wanneer f123() wordt aangeroepen, geeft het geen van de waarden terug in de rendementsverklaringen! Het retourneert een generatorobject. Ook wordt de functie niet echt afgesloten – hij gaat in een onderbroken toestand. Wanneer de for-lus probeert over het generatorobject te lussen, hervat de functie vanuit zijn onderbroken toestand op de volgende regel na de yield waaruit het eerder terugkeerde, voert de volgende regel uit van code, in dit geval een yield-statement, en retourneert dat als het volgende item. Dit gebeurt totdat de functie wordt afgesloten, waarna de generator StopIteration verhoogt en de lus wordt afgesloten.

Het generator-object is dus een soort adapter – aan de ene kant vertoont het het iterator-protocol, door de methoden __iter__() en next() bloot te leggen om de for lus gelukkig. Aan de andere kant voert het de functie echter net genoeg uit om de volgende waarde eruit te halen, en zet het terug in de onderbroken modus.

Waarom generatoren gebruiken?

Meestal kun je code schrijven die geen generatoren gebruikt, maar dezelfde logica implementeert. Een optie is om de tijdelijke lijst ‘truc’ te gebruiken die ik eerder noemde. Dat zal niet in alle gevallen werken, voor b.v. als je oneindige lussen hebt, of het kan inefficiënt gebruik maken van het geheugen als je een erg lange lijst hebt. De andere benadering is om een ​​nieuwe itereerbare klasse SomethingIter te implementeren die de status in instantieleden houdt en de volgende logische stap uitvoert in zijn next() (of __next__() in Python 3) methode. Afhankelijk van de logica kan de code in de next()-methode er erg complex uitzien en vatbaar zijn voor bugs. Hier bieden generatoren een schone en gemakkelijke oplossing.


Antwoord 3, autoriteit 4%

Zie het zo:

Een iterator is gewoon een mooi klinkende term voor een object dat een next()-methode heeft. Dus een functie met opbrengst wordt uiteindelijk zoiets als dit:

Originele versie:

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

Dit is eigenlijk 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, kan de for-lus hierin worden herschreven:

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

Is dat logischer of brengt het je alleen maar meer in de war? 🙂

Ik moet er rekening mee houden dat dit is een te grote vereenvoudiging voor illustratieve doeleinden. 🙂


Antwoord 4, autoriteit 3%

Het yield zoekwoord is teruggebracht tot twee simpele feiten:

  1. Als de compiler het yield trefwoord anywhere in een functie detecteert, retourneert die functie niet langer via de return-instructie. In plaats daarvan retourneert het onmiddellijk een lui “in behandeling zijnde lijst” object genaamd een generator
  2. Een generator is itereerbaar. 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, incrementeel wachtende lijst, en yield-statements stellen je in staat functienotatie te 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 makeRange definiëren die net als het range van Python is. Aanroepen van makeRange(n) RETOURNEERT 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 zijn lopende waarden onmiddellijk terug te geven, kun je deze doorgeven aan list() (net zoals je zou kunnen herhalen):

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

Vergelijkend voorbeeld met “gewoon een lijst retourneren”

Het bovenstaande voorbeeld kan worden gezien als het maken van een lijst die u toevoegt 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 één groot verschil; zie de laatste sectie.


Hoe u generatoren zou kunnen gebruiken

Een iterabel is het laatste deel van het begrip van een lijst, en alle generatoren zijn itereerbaar, dus ze worden vaak als volgt gebruikt:

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

Om een ​​beter idee te krijgen van generatoren, kun je spelen met de module itertools (gebruik chain.from_iterable in plaats van chain wanneer gerechtvaardigd). U kunt bijvoorbeeld zelfs generatoren gebruiken om oneindig lange luie lijsten zoals itertools.count() te implementeren. U kunt uw eigen def enumerate(iterable): zip(count(), iterable) implementeren, of u kunt dit ook doen met het trefwoord yield in een while-loop.

Let op: generatoren kunnen eigenlijk voor veel meer dingen worden gebruikt, zoals het implementeren van coroutines of niet-deterministische programmering of andere elegante dingen. Het gezichtspunt van “luie lijsten” 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 wil zeggen, wat er gebeurt als u list(makeRange(5)) doet. Dit is wat ik eerder beschrijf 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 functie objects .next() aan, die deel uitmaakt van het “iteratieprotocol” en op alle iteratoren. Je kunt handmatig de functie next() (en andere delen van het iteratieprotocol) gebruiken om mooie dingen te implementeren, meestal ten koste van de leesbaarheid, dus probeer dat te vermijden…


Minutiae

Normaal gesproken zouden de meeste mensen niet geïnteresseerd zijn in de volgende verschillen en willen ze waarschijnlijk hier stoppen met lezen.

In Python-speak is een iterable elk object dat “het concept van een for-loop begrijpt”, zoals een lijst [1,2,3], en een iterator is een specifieke instantie van de gevraagde for-loop zoals [1,2,3].__iter__(). Een generator is precies hetzelfde als elke iterator, behalve de manier waarop deze is geschreven (met functiesyntaxis).

Als je een iterator uit een lijst opvraagt, wordt er een nieuwe iterator gemaakt. Wanneer u echter een iterator van een iterator aanvraagt ​​(wat u zelden zou doen), geeft het u gewoon een kopie van zichzelf.

Dus, in het onwaarschijnlijke geval dat u iets als dit niet doet…

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

… onthoud dan dat een generator een iterator is; dat wil zeggen, het is eenmalig te gebruiken. Als je het opnieuw wilt gebruiken, moet je myRange(...) opnieuw aanroepen. Als u het resultaat twee keer moet 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 (die bijvoorbeeld angstaanjagende hackachtige metaprogrammering doen), kunnen itertools.tee indien absoluut noodzakelijk, aangezien de kopieerbare iterator Python PEP-standaardvoorstel is uitgesteld.


Antwoord 5, autoriteit 3%

Wat doet het trefwoord yield in Python?

Antwoordoverzicht/samenvatting

  • Een functie met yield , wanneer aangeroepen, retourneert een generator.
  • Generators zijn iterators omdat ze het iteratorprotocol implementeren strong>, zodat u ze kunt herhalen.
  • Een generator kan ook informatie worden verzonden, waardoor het conceptueel een coroutine wordt.
  • In Python 3 kun je delegeren van de ene generator naar de andere in beide richtingen met yield from.
  • (Bijlage bekritiseert een aantal antwoorden, waaronder 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 zorgt ervoor dat het een generator.

Het idee voor generatoren komt uit andere talen (zie voetnoot 1) met verschillende implementaties. In Python’s Generators wordt de uitvoering van de code bevroren aan de punt van de opbrengst. Wanneer de generator wordt aangeroepen (methoden worden hieronder besproken), wordt de uitvoering hervat en bevriest vervolgens bij de volgende opbrengst.

yield biedt een
gemakkelijke manier om het iteratorprotocol te implementeren, gedefinieerd door de volgende twee methoden:
__iter__ en next (Python 2) of __next__ (Python 3). Beide methoden
maak van een object een iterator die je zou kunnen type-checken met de iterator abstracte basis
Les uit de module collections.

>>> 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 de typecontrole als volgt uitvoeren:

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

Een kenmerk van een iterator is dat deze eenmaal uitgeput is , je kunt het niet opnieuw gebruiken of resetten:

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

Je moet er nog een maken als je de functionaliteit ervan weer wilt gebruiken (zie voetnoot 2):

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

Men kan programmatisch gegevens 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) kun je yield from:

def func(an_iterable):
    yield from an_iterable

Echter, yield from staat ook delegatie toe aan subgeneratoren,
die zal worden uitgelegd in het volgende gedeelte over coöperatieve delegatie met subcoroutines.

Coroutines:

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

Hier is een voorbeeld, let op de variabele received, 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
roep de juiste next of __next__ methode aan, afhankelijk van de versie van
Python die je gebruikt:

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

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

>>> 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 nu dat yield from beschikbaar is in Python 3. Dit stelt ons in staat coroutines te delegeren aan een subcoroutine:


def money_manager(expected_rate):
    # must receive deposited value from .send():
    under_management = yield                   # yield None to start.
    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'''
            raise
        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'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

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

my_manager = money_manager(.06)
my_account = investment_account(1000, my_manager)
first_year_return = next(my_account) # -> 60.0

Suleer nu het toevoegen van nog eens 1.000 aan het account plus het rendement op het account (60,0):

next_year_return = my_account.send(first_year_return + 1000)
next_year_return # 123.6

Je 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 waar de functie
executie werd bevroren. Dit wordt ook aangeroepen door __del__ dus jij
kan elke opschoningscode plaatsen waar u de GeneratorExit afhandelt:

my_account.close()

Je kunt ook een uitzondering maken die in de generator kan worden afgehandeld
of teruggestuurd naar de gebruiker:

import sys
try:
    raise ValueError
except:
    my_manager.throw(*sys.exc_info())

Verhoogt:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 6, in money_manager
  File "<stdin>", line 2, in <module>
ValueError

Conclusie

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

Wat doet het trefwoord yield in Python?

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


Bijlage:

Kritiek van het beste/geaccepteerde antwoord**

  • Het is niet duidelijk wat een itereerbaar maakt, we gebruiken alleen een lijst als voorbeeld. Zie mijn referenties hierboven, maar samenvattend: een iterable heeft een __iter__ methode die een iterator retourneert. Een iterator levert een .next (Python 2 of .__next__ (Python 3) methode, die impliciet wordt aangeroepen door for loopt door totdat het StopIteration verhoogt, en als het dat doet, blijft het dat doen.
  • Het gebruikt dan een generator-expressie om te beschrijven wat een generator is. Aangezien een generator gewoon een handige manier is om een ​​iterator te maken, verwart het de zaak alleen maar, en we zijn nog steeds niet bij het gedeelte yield gekomen.
  • In Het beheersen van de uitputting van een generator roept hij de .next methode aan, terwijl hij in plaats daarvan de ingebouwde functie next moet gebruiken. Het zou een geschikte indirecte laag zijn, omdat zijn code niet werkt in Python 3.
  • Itertools? Dit was helemaal niet relevant voor wat yield doet.
  • Geen discussie over de methoden die yield biedt, samen met de nieuwe functionaliteit yield from in Python 3. Het beste/geaccepteerde antwoord is een zeer onvolledig antwoord.

Kritiek van antwoord dat yield suggereert 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, wordt het door sommigen als interessant aangeprezen om het te gebruiken in bevattingen of generatoruitdrukkingen, ondanks het feit dat er geen bijzonder goede use-case wordt genoemd.

De kernontwikkelaars van CPython zijn over het afschaffen van de vergoeding.
Hier is een relevant bericht van de mailinglijst:

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

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

Ik ben akkoord met beide benaderingen. De dingen laten zoals ze zijn in Python 3
is niet goed, IMHO.

Mijn stem is dat het een syntaxisfout is, omdat je niet krijgt wat je verwacht
de syntaxis.

Ik ben het ermee eens dat dit voor ons een verstandige plaats is om te eindigen, zoals elke code
vertrouwen op het huidige gedrag is echt te slim om te zijn
onderhoudbaar.

Om daar te komen, willen we waarschijnlijk:

  • SyntaxWarning of DeprecationWarning in 3.7
  • Py3k-waarschuwing in 2.7.x
  • Syntaxfout in 3.8

Proost, Nick.

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

Verder is er een openstaand probleem (10544) dat in de richting van dit lijkt te wijzen nooit een goed idee zijn (PyPy, een Python-implementatie geschreven in Python, geeft al syntaxiswaarschuwingen.)

Bottom line, totdat de ontwikkelaars van CPython ons anders vertellen: Plaats yield niet in een generatoruitdrukking of begrip.

Het return statement in een generator

In Python 2:

In een generatorfunctie mag de return-instructie geen expression_list bevatten. In die context geeft een kale return aan dat de generator klaar is en ervoor zorgt dat StopIteration wordt verhoogd.

Een expression_list is in feite een willekeurig aantal expressies gescheiden door komma’s – in wezen kun je in Python 2 de generator stoppen met return, maar je kunt geen waarde.

In Python 3:

In een generatorfunctie geeft de return-instructie aan dat de generator klaar is en ervoor zorgt dat StopIteration wordt gegenereerd. De geretourneerde waarde (indien aanwezig) wordt gebruikt als argument om StopIteration te construeren en wordt het StopIteration.value-kenmerk.

Voetnoten

  1. De talen CLU, Sather en Icon werden genoemd in het voorstel
    om het concept van generatoren in Python te introduceren. Het algemene idee is:
    dat een functie de interne toestand kan behouden en intermediair kan opleveren
    datapunten op aanvraag van de gebruiker. Dit beloofde superieure prestaties te zijn
    naar andere benaderingen, waaronder Python-threading
    , die op sommige systemen niet eens beschikbaar is.

  2. Dit betekent bijvoorbeeld dat range-objecten geen iterators zijn, ook al zijn ze itereerbaar, omdat ze opnieuw kunnen worden gebruikt. Net als lijsten, retourneren hun __iter__ methoden iterator-objecten.

yield werd oorspronkelijk geïntroduceerd als een statement, wat betekent dat het
kon alleen verschijnen aan het begin van een regel in een codeblok.
Nu maakt yield een opbrengstexpressie.
https://docs.python.org/2/reference/simple_stmts .html#grammar-token-yield_stmt
Deze wijziging is voorgesteld om een gebruiker om gegevens naar de generator te sturen, net zoals:
men zou het kunnen ontvangen. Om gegevens te verzenden, moet men ze ergens aan kunnen toewijzen, en
daarvoor werkt een statement gewoon niet.


Antwoord 6, autoriteit 2%

yield is net als return – het geeft terug wat je het vertelt (als een generator). Het verschil is dat de volgende keer dat u de generator aanroept, de uitvoering begint vanaf de laatste aanroep tot de yield-instructie. In tegenstelling tot return, wordt het stackframe niet opgeschoond wanneer een opbrengst optreedt, maar de controle wordt terug overgedragen aan de beller, zodat de status wordt hervat wanneer de functie de volgende keer wordt aangeroepen.

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

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


Antwoord 7, autoriteit 2%

Er is één extra ding om te vermelden: een functie die oplevert, hoeft eigenlijk niet te eindigen. Ik heb de code als volgt geschreven:

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

Dan kan ik het in andere code gebruiken, zoals deze:

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.


Antwoord 8, autoriteit 2%

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

Antwoord 9

TL;DR

In plaats van dit:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

doe dit:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Telkens wanneer je merkt dat je een lijst helemaal opnieuw opbouwt, yield elk stuk.

Dit was mijn eerste “aha”-moment met opbrengst.


yield is een suikerachtige manier om te zeggen

bouw een reeks dingen

Hetzelfde gedrag:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Ander gedrag:

Opbrengst is single-pass: je kunt maar één keer herhalen. Als een functie een opbrengst heeft, noemen we het een generatorfunctie. En een iterator is wat het retourneert. Die termen zijn onthullend. We verliezen het gemak van een container, maar krijgen de kracht van een reeks die naar behoefte wordt berekend en willekeurig lang is.

Opbrengst is lui, het stelt de berekening uit. Een functie met een opbrengst erin wordt helemaal niet uitgevoerd als je hem aanroept. Het retourneert een iterator-object dat onthoudt waar het was gebleven. Elke keer dat u next() aanroept in de iterator (dit gebeurt in een for-loop), gaat de uitvoering centimeters vooruit naar de volgende opbrengst. return verhoogt StopIteration en beëindigt de serie (dit is het natuurlijke einde van een for-loop).

Het rendement is veelzijdig. Gegevens hoeven niet allemaal bij elkaar te worden opgeslagen, ze kunnen één voor één beschikbaar worden gesteld. Het kan oneindig zijn.

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Als je meerdere passen nodig hebt en de serie niet te lang is, bel dan gewoon list() erop:

>>> list(square_yield(4))
[0, 1, 4, 9]

Briljante keuze van het woord yield omdat beide betekenissen toepassen:

opbrengst — produceren of leveren (zoals in de landbouw)

…geef de volgende gegevens in de reeks.

opbrengst — wijken of afstaan ​​(zoals bij politieke macht)

…laat de CPU-uitvoering los totdat de iterator vordert.


Antwoord 10

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 kunt zien, houdt foo in het eerste geval de hele lijst in één keer in het geheugen. Het is niet erg voor een lijst met 5 elementen, maar wat als je een lijst van 5 miljoen wilt? Dit is niet alleen een enorme geheugenvreter, het kost ook veel tijd om te bouwen op het moment dat de functie wordt aangeroepen.

In het tweede geval geeft bar je gewoon een generator. Een generator is een iterabel – wat betekent dat je hem kunt gebruiken in een for-lus, enz., maar elke waarde is slechts één keer toegankelijk. Ook worden niet alle waarden 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, als je een iterabel gebruikt om (laten we zeggen) te tellen tot 50 miljard, hoef je niet allemaal tot 50 miljard te tellen tegelijk en sla de 50 miljard nummers op om door te tellen.

Nogmaals, dit is een behoorlijk gekunsteld voorbeeld, je zou waarschijnlijk itertools gebruiken als je echt tot 50 miljard zou willen tellen. 🙂

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


Antwoord 11

Het stuurt een generator terug. Ik ben niet echt bekend met Python, maar ik geloof dat het hetzelfde is als C#’s iteratorblokken als je daar bekend mee bent.

Het belangrijkste idee is dat de compiler/interpreter/wat dan ook wat bedrog doet, zodat wat de beller betreft, ze next() kunnen blijven aanroepen en het blijft waarden retourneren – alsof de generatormethode was onderbroken. Het is duidelijk dat u een methode niet echt kunt “pauzeren”, dus bouwt de compiler een toestandsmachine voor u om te onthouden waar u zich momenteel bevindt en hoe de lokale variabelen enz. eruit zien. Dit is veel gemakkelijker dan zelf een iterator te schrijven.


Antwoord 12

Er is één type antwoord dat volgens mij nog niet is gegeven, onder de vele goede antwoorden die beschrijven hoe generatoren moeten worden gebruikt. Hier is het antwoord op de programmeertaaltheorie:

De yield-instructie in Python retourneert een generator. Een generator in Python is een functie die voortzettingen retourneert (en specifiek een type coroutine, maar voortzettingen vertegenwoordigen het meer algemene mechanisme om te begrijpen wat er aan de hand is).

Voortzettingen in de theorie van programmeertalen zijn een veel fundamentelere vorm van berekeningen, maar ze worden niet vaak gebruikt, omdat ze extreem moeilijk te redeneren zijn en ook erg moeilijk te implementeren. Maar het idee van wat een voortzetting is, is eenvoudig: het is de staat van een berekening die nog niet is voltooid. In deze status worden de huidige waarden van variabelen, de bewerkingen die nog moeten worden uitgevoerd, enzovoort, opgeslagen. Dan kan op een bepaald punt later in het programma de voortzetting worden aangeroepen, zodat de variabelen van het programma in die staat worden gereset en de bewerkingen die zijn opgeslagen worden uitgevoerd.

Vervolgingen, in deze meer algemene vorm, kunnen op twee manieren worden geïmplementeerd. Op de call/cc manier wordt de stapel van het programma letterlijk opgeslagen en wanneer de voortzetting wordt aangeroepen, wordt de stapel hersteld.

In continuation passing style (CPS) zijn voortzettingen gewoon normale functies (alleen in talen waar functies eersteklas zijn) die de programmeur expliciet beheert en doorgeeft aan subroutines. In deze stijl wordt de programmastatus weergegeven door sluitingen (en de variabelen die er toevallig in zijn gecodeerd) in plaats van variabelen die zich ergens op de stapel bevinden. Functies die de controlestroom beheren, accepteren voortzetting als argumenten (in sommige variaties van CPS kunnen functies meerdere voortzettingen accepteren) en manipuleren de controlestroom door ze aan te roepen door ze eenvoudigweg aan te roepen en daarna terug te keren. Een heel eenvoudig voorbeeld van een vervolg-passingstijl is als volgt:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)
  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

In dit (zeer simplistische) voorbeeld slaat de programmeur de bewerking van het daadwerkelijk schrijven van het bestand in een vervolg op (wat mogelijk een zeer complexe bewerking kan zijn met veel details om weg te schrijven), en geeft dan die voortzetting door (dwz als een eersteklas afsluiting) naar een andere operator die wat meer verwerking doet, en deze indien nodig aanroept. (Ik gebruik dit ontwerppatroon veel bij het programmeren van GUI’s, hetzij omdat het me regels code bespaart of, nog belangrijker, om de controlestroom te beheren nadat GUI-gebeurtenissen zijn geactiveerd.)

De rest van dit bericht zal, zonder verlies van algemeenheid, voortzettingen als CPS beschouwen, omdat het een stuk gemakkelijker te begrijpen en te lezen is.

Laten we het nu hebben over generatoren in Python. Generatoren zijn een specifiek subtype van voortzetting. Terwijl voortzettingen in het algemeen de staat van een berekening (dwz de aanroepstack van het programma) kunnen opslaan, zijn generatoren alleen in staat om de iteratiestatus over een iterator. Hoewel deze definitie enigszins misleidend is voor bepaalde use-cases van generatoren. Bijvoorbeeld:

def f():
  while True:
    yield 4

Dit is duidelijk een redelijke iterabel waarvan het gedrag goed gedefinieerd is — elke keer dat de generator het herhaalt, retourneert het 4 (en doet dit voor altijd). Maar het is waarschijnlijk niet het prototypische type iterable dat in je opkomt als je aan iterators denkt (d.w.z. for x in collection: do_something(x)). Dit voorbeeld illustreert de kracht van generatoren: als iets een iterator is, kan een generator de status van zijn iteratie opslaan.

Te herhalen: voortzettingen kunnen de status van de stapel van een programma opslaan en generatoren kunnen de herhalingsstatus opslaan. Dit betekent dat voortzettingen veel krachtiger zijn dan generatoren, maar ook dat generatoren een stuk, veel gemakkelijker zijn. Ze zijn gemakkelijker voor de taalontwerper om te implementeren en ze zijn gemakkelijker voor de programmeur om te gebruiken (als je wat tijd hebt om te branden, probeer dan deze pagina over vervolg en call/cc).

Maar je zou generators gemakkelijk kunnen implementeren (en conceptualiseren) als een eenvoudig, specifiek geval van voortzetting van het doorgeven:

Telkens wanneer yield wordt aangeroepen, vertelt het de functie om een ​​vervolg te retourneren. Wanneer de functie opnieuw wordt aangeroepen, begint deze waar hij was gebleven. Dus in pseudo-pseudocode (d.w.z. niet pseudocode, maar geen code) is de next-methode van de generator in principe als volgt:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)
  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

waar het yield trefwoord eigenlijk syntactische suiker is voor de echte generatorfunctie, in feite zoiets als:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Onthoud dat dit slechts pseudocode is en dat de daadwerkelijke implementatie van generatoren in Python complexer is. Maar als oefening om te begrijpen wat er aan de hand is, kunt u proberen de stijl van voortzetting door te geven om generatorobjecten te implementeren zonder het trefwoord yield te gebruiken.


Antwoord 13

Hier is een voorbeeld in gewone taal. Ik zal zorgen voor een overeenkomst tussen menselijke concepten op hoog niveau en Python-concepten op laag niveau.

Ik wil werken met een reeks getallen, maar ik wil mezelf niet lastigvallen met het maken van die reeks, ik wil me alleen concentreren op de bewerking die ik wil doen. Dus doe ik het volgende:

  • Ik bel je en vertel je dat ik een reeks getallen wil die op een specifieke manier wordt geproduceerd, en ik laat je weten wat het algoritme is.
    Deze stap komt overeen met het defin de generatorfunctie, d.w.z. de functie die een yield bevat.
  • Enige tijd later zeg ik je: “Oké, maak je klaar om me de reeks getallen te vertellen”.
    Deze stap komt overeen met het aanroepen van de generatorfunctie die een generatorobject retourneert. Merk op dat je me nog geen getallen vertelt; je pakt gewoon je papier en potlood.
  • Ik vraag je, “vertel me het volgende nummer”, en jij vertelt me ​​het eerste nummer; daarna wacht je tot ik je om het volgende nummer vraag. Het is jouw taak om te onthouden waar je was, welke nummers je al hebt gezegd en wat het volgende nummer is. Het gaat mij niet om de details.
    Deze stap komt overeen met het aanroepen van .next() op het generatorobject.
  • herhaal de vorige stap, totdat
  • uiteindelijk kom je misschien tot een einde. Je vertelt me ​​geen nummer; je schreeuwt gewoon, “houd je paarden vast! Ik ben klaar! Geen nummers meer!”
    Deze stap komt overeen met het generatorobject dat zijn taak beëindigt en een StopIteration-uitzondering opheft. De generatorfunctie hoeft de uitzondering niet op te heffen. Het wordt automatisch verhoogd wanneer de functie eindigt of een return afgeeft.

Dit is wat een generator doet (een functie die een yield bevat); het begint met uitvoeren, pauzeert wanneer het een yield doet, en wanneer gevraagd wordt om een ​​.next() waarde, gaat het verder vanaf het punt waar het de laatste keer was. Het past qua ontwerp perfect bij het iteratorprotocol van Python, dat beschrijft hoe sequentieel waarden opgevraagd kunnen worden.

De meest bekende gebruiker van het iterator-protocol is de opdracht for in Python. Dus, wanneer u een:

for item in sequence:

het maakt niet uit of sequence een lijst, een string, een woordenboek of een generator object is zoals hierboven beschreven; het resultaat is hetzelfde: je leest de items een voor een af.

Merk op dat het defin een functie die een yield trefwoord bevat, niet de enige manier is om een ​​generator te maken; het is gewoon de gemakkelijkste manier om er een te maken.

Lees voor meer accurate informatie over iteratortypen, de opbrengstverklaring en generatoren in de Python-documentatie.


Antwoord 14

Hoewel veel antwoorden laten zien waarom je een yield zou gebruiken om een ​​generator te maken, zijn er meer toepassingen voor yield. Het is vrij eenvoudig om een ​​coroutine te maken, waarmee informatie tussen twee codeblokken kan worden doorgegeven. Ik zal geen van de mooie voorbeelden herhalen die al zijn gegeven over het gebruik van yield om een ​​generator te maken.

Om te begrijpen wat een yield doet in de volgende code, kun je je vinger gebruiken om de cyclus te volgen door elke code die een yield heeft. Elke keer dat uw vinger de yield raakt, moet u wachten op een next of een send om in te voeren. Wanneer een next wordt aangeroepen, volg je de code totdat je de yield hebt bereikt de code rechts van de yield wordt geëvalueerd en geretourneerd naar de beller dan wacht u. Wanneer next opnieuw wordt aangeroepen, voer je nog een lus door de code uit. U zult echter opmerken dat in een coroutine, yield ook kan worden gebruikt met een send die een waarde van de beller in zal sturen de meegevende functie. Als een send wordt gegeven, dan ontvangt yield de verzonden waarde en spuugt deze uit aan de linkerkant dan gaat de tracering door de code verder totdat u op de yield opnieuw (waarbij de waarde aan het einde wordt geretourneerd, alsof next werd aangeroepen).

Bijvoorbeeld:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()

Antwoord 15

Er is nog een yield gebruik en betekenis (sinds Python 3.3):

yield from <expr>

Van PEP 380 — Syntaxis voor delegeren aan een subgenerator:

Er wordt een syntaxis voorgesteld voor een generator om een ​​deel van zijn bewerkingen aan een andere generator te delegeren. Hierdoor kan een codegedeelte met ‘opbrengst’ worden weggelaten en in een andere generator worden geplaatst. Bovendien mag de subgenerator terugkeren met een waarde en wordt de waarde beschikbaar gesteld aan de delegerende generator.

De nieuwe syntaxis biedt ook enkele mogelijkheden voor optimalisatie wanneer een generator waarden oplevert die door een andere zijn geproduceerd.

Bovendien dit introduceert (sinds Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

om te voorkomen dat coroutines worden verward met een gewone generator (vandaag wordt yield in beide gebruikt).


Antwoord 16

Allemaal geweldige antwoorden, maar een beetje moeilijk voor beginners.

Ik neem aan dat je het return statement hebt geleerd.

Als analogie zijn return en yield tweelingen. return betekent ‘retour en stop’ terwijl ‘yield’ betekent ‘retour, maar doorgaan’

  1. Probeer een num_list te krijgen met return.
def num_list(n):
    for i in range(n):
        return i

Voer het uit:

In [5]: num_list(3)
Out[5]: 0

Kijk, je krijgt maar één nummer in plaats van een lijst ervan. return laat je nooit gelukkig zegevieren, implementeert slechts één keer en stop.

  1. Er komt yield

Vervang return door yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:
In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>
In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Nu win je om alle nummers te krijgen.

Vergeleken met return die één keer wordt uitgevoerd en stopt, wordt yield uitgevoerd op de tijd die je hebt gepland.
U kunt return interpreteren als return one of them en yield als return all of them. Dit wordt iterable genoemd.

  1. Nog een stap kunnen we het yield statement herschrijven met return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]

Het is de kern van yield.

Het verschil tussen een lijst return-uitgangen en de object yield-uitgang is:

Je krijgt altijd [0, 1, 2] van een lijstobject, maar je kunt ze maar één keer ophalen uit ‘de uitvoer van het object yield‘. Het heeft dus een nieuwe naam generator object zoals weergegeven in Out[11]: <generator object num_list at 0x10327c990>.

Tot slot, als een metafoor om het te grommen:

  • return en yield zijn een tweeling
  • list en generator zijn een tweeling

Antwoord 17

Vanuit een programmeringsoogpunt worden de iterators geïmplementeerd als thunks.

Om iterators, generatoren en threadpools voor gelijktijdige uitvoering, enz. als thunks te implementeren, gebruikt men berichten die naar een sluitingsobject, dat een verzender heeft, en de verzender antwoordt op "berichten".

"volgende" is een bericht dat naar een afsluiting wordt gestuurd, gemaakt door de "iter" bellen.

Er zijn veel manieren om deze berekening te implementeren. Ik heb mutatie gebruikt, maar het is mogelijk om dit soort berekeningen zonder mutatie uit te voeren, door de huidige waarde en de volgende opbrengst terug te geven (waardoor het referentieel transparant). Racket gebruikt een reeks transformaties van het oorspronkelijke programma in sommige intermediaire talen, waarbij een van dergelijke herschrijvingen ervoor zorgt dat de opbrengst-operator in een of andere taal wordt getransformeerd met eenvoudigere operators.

Hier is een demonstratie van hoe de opbrengst kan worden herschreven, waarbij de structuur van R6RS wordt gebruikt, maar de semantiek is identiek aan die van Python. Het is hetzelfde berekeningsmodel en er is alleen een wijziging in de syntaxis nodig om het te herschrijven met de opbrengst van Python.

Welcome to Racket v6.5.0.3.
-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->

Antwoord 18

Hier zijn enkele Python-voorbeelden van hoe generators daadwerkelijk kunnen worden geïmplementeerd alsof Python er geen syntactische suiker voor heeft geleverd:

Als Python-generator:

from itertools import islice
def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b
assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Lexicale sluitingen gebruiken in plaats van generatoren

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]
def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _
assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Objectafsluitingen gebruiken in plaats van generatoren (omdat ClosuresAndObjectsAreEquivalent)

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1
    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r
assert [1,1,2,3,5] == ftake(fib_gen3(), 5)

Antwoord 19

Ik wilde “lees pagina 19 van Beazley’s ‘Python: Essential Reference’ voor een korte beschrijving van generatoren” posten, maar zoveel anderen hebben al goede beschrijvingen gepost.

Houd er ook rekening mee dat yield kan worden gebruikt in coroutines als het dubbele van hun gebruik in generatorfuncties. Hoewel het niet hetzelfde gebruik is als uw codefragment, kan (yield) worden gebruikt als een uitdrukking in een functie. Wanneer een aanroeper een waarde naar de methode stuurt met behulp van de send() methode, dan zal de coroutine worden uitgevoerd totdat de volgende (yield)-instructie wordt aangetroffen.

Generators en coroutines zijn een coole manier om toepassingen van het type datastroom in te stellen. Ik dacht dat het de moeite waard zou zijn om meer te weten over het andere gebruik van de yield-instructie in functies.


Antwoord 20

Hier is een eenvoudig voorbeeld:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True
def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1
for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Uitvoer:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Ik ben geen Python-ontwikkelaar, maar het lijkt mij dat yield de positie van de programmastroom vasthoudt en de volgende lus start vanaf de “yield”-positie. Het lijkt alsof hij op die positie wacht, en net daarvoor een waarde naar buiten teruggeeft, en de volgende keer blijft werken.

Het lijkt een interessante en mooie vaardigheid te zijn 😀


Antwoord 21

Hier is een mentaal beeld van wat yield doet.

Ik zie een thread graag als een stapel (zelfs als het niet op die manier is geïmplementeerd).

Als een normale functie wordt aangeroepen, plaatst deze zijn lokale variabelen op de stapel, doet wat berekeningen, maakt de stapel leeg en geeft terug. De waarden van zijn lokale variabelen worden nooit meer gezien.

Met een functie yield, wanneer de code begint te lopen (dwz nadat de functie is aangeroepen, wordt een generatorobject geretourneerd, waarvan de methode next() vervolgens wordt aangeroepen) , het plaatst op dezelfde manier zijn lokale variabelen op de stapel en berekent een tijdje. Maar dan, wanneer het de yield-instructie bereikt, voordat het zijn deel van de stapel wist en terugkeert, maakt het een momentopname van zijn lokale variabelen en slaat deze op in het generatorobject. Het schrijft ook de plaats op waar het momenteel mee bezig is in zijn code (d.w.z. de specifieke yield-instructie).

Het is dus een soort bevroren functie waar de generator aan blijft hangen.

Wanneer next() vervolgens wordt aangeroepen, worden de eigendommen van de functie op de stapel opgehaald en opnieuw geanimeerd. De functie rekent verder vanaf het punt waar hij was gebleven, zich niet bewust van het feit dat hij net een eeuwigheid in de koeling had doorgebracht.

Vergelijk de volgende voorbeelden:

def normalFunction():
    return
    if False:
        pass
def yielderFunction():
    return
    if False:
        yield 12

Als we de tweede functie aanroepen, gedraagt ​​deze zich heel anders dan de eerste. Het yield-statement is misschien onbereikbaar, maar als het ergens aanwezig is, verandert het de aard van waar we mee te maken hebben.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Het aanroepen van yielderFunction() voert zijn code niet uit, maar maakt een generator van de code. (Misschien is het een goed idee om zulke dingen te noemen met het voorvoegsel yielder voor de leesbaarheid.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

In de velden gi_code en gi_frame wordt de bevroren status opgeslagen. Als we ze onderzoeken met dir(..), kunnen we bevestigen dat ons mentale model hierboven geloofwaardig is.


Antwoord 22

Een eenvoudig voorbeeld om te begrijpen wat het is: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2
for i in f123():
    print (i)

De uitvoer is:

1 2 1 2 1 2 1 2

Antwoord 23

Zoals elk antwoord suggereert, wordt yield gebruikt voor het maken van een reeksgenerator. Het wordt gebruikt voor het dynamisch genereren van een reeks. Als u bijvoorbeeld een bestand regel voor regel op een netwerk leest, kunt u de functie yield als volgt gebruiken:

def getNextLines():
   while con.isOpen():
       yield con.read()

Je kunt het als volgt in je code gebruiken:

for line in getNextLines():
    doSomeThing(line)

Execution Control Transfer gotcha

De uitvoeringscontrole wordt overgedragen van getNextLines() naar de for-lus wanneer opbrengst wordt uitgevoerd. Dus elke keer dat getNextLines() wordt aangeroepen, begint de uitvoering vanaf het punt waar het de vorige keer werd onderbroken.

In het kort dus een functie met de volgende code

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)
for i in simpleYield():
    print i

wordt afgedrukt

"first time"
"second time"
"third time"
"Now some useful value 12"

Antwoord 24

(Mijn onderstaande antwoord spreekt alleen vanuit het perspectief van het gebruik van de Python-generator, niet de onderliggende implementatie van generatormechanisme, waarbij enkele trucjes van stapel- en heapmanipulatie nodig zijn.)

Wanneer yield wordt gebruikt in plaats van een return in een python-functie, wordt die functie omgezet in iets speciaals genaamd generator function. Die functie retourneert een object van het type generator. Het yield sleutelwoord is een vlag om de python-compiler op de hoogte te stellen om een ​​dergelijke functie speciaal te behandelen. Normale functies worden beëindigd zodra er een waarde uit wordt teruggegeven. Maar met behulp van de compiler kan de generatorfunctie worden beschouwd als hervatbaar. Dat wil zeggen, de uitvoeringscontext wordt hersteld en de uitvoering wordt voortgezet vanaf de laatste uitvoering. Totdat je expliciet return aanroept, wat een StopIteration-uitzondering oplevert (die ook deel uitmaakt van het iteratorprotocol), of het einde van de functie bereikt. Ik heb veel verwijzingen gevonden over generator, maar deze een vanuit het functional programming perspective is het meest verteerbaar.

(Nu wil ik het hebben over de grondgedachte achter generator en de iterator op basis van mijn eigen begrip. Ik hoop dat dit je kan helpen de essentiële motivatie van iterator en generator. Een dergelijk concept komt ook voor in andere talen, zoals C#.)

Zoals ik begrijp, slaan we, wanneer we een heleboel gegevens willen verwerken, de gegevens meestal eerst ergens op en verwerken ze dan één voor één. Maar deze naïeve benadering is problematisch. Als het datavolume enorm is, is het duur om ze vooraf als geheel op te slaan. Dus in plaats van de data zelf direct op te slaan, waarom niet een soort metadata indirect opslaan, dat wil zeggen the logic how the data is computed .

Er zijn twee manieren om dergelijke metadata in te pakken.

  1. De OO-benadering, we verpakken de metadata as a class. Dit is de zogenaamde iterator die het iteratorprotocol implementeert (d.w.z. de __next__() en __iter__() methoden). Dit is ook het vaak voorkomende iterator-ontwerppatroon.
  2. De functionele benadering, we verpakken de metadata as a function. Dit is
    de zogenaamde generator function. Maar onder de motorkap blijft het geretourneerde generator object nog steeds IS-A iterator omdat het ook het iteratorprotocol implementeert.

Hoe dan ook, er wordt een iterator gemaakt, d.w.z. een object dat u de gewenste gegevens kan geven. De OO-aanpak is misschien een beetje ingewikkeld. Hoe dan ook, welke je moet gebruiken, is aan jou.


Antwoord 25

Samengevat, de yield-instructie transformeert uw functie in een fabriek die een speciaal object produceert, een generator genaamd, die zich om de hoofdtekst van uw oorspronkelijke functie wikkelt. Wanneer de generator wordt herhaald, voert deze uw functie uit totdat deze de volgende yield bereikt, schorst vervolgens de uitvoering en evalueert tot de waarde die is doorgegeven aan yield. Het herhaalt dit proces bij elke iteratie totdat het uitvoeringspad de functie verlaat. Bijvoorbeeld,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'
for i in simple_generator():
    print i

gewoon uitvoer

one
two
three

Het vermogen komt van het gebruik van de generator met een lus die een reeks berekent, de generator voert de lus uit en stopt elke keer om het volgende resultaat van de berekening ‘op te leveren’, op deze manier berekent het een lijst on the fly, het voordeel het geheugen dat wordt opgeslagen voor bijzonder grote berekeningen

Stel dat je je eigen range-functie wilde maken die een itereerbaar bereik van getallen produceert, je zou het zo kunnen doen,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

en gebruik het als volgt;

for i in myRangeNaive(10):
    print i

Maar dit is inefficiënt omdat

  • Je maakt een array die je maar één keer gebruikt (dit verspilt geheugen)
  • Deze code loopt eigenlijk twee keer over die array! 🙁

Gelukkig waren Guido en zijn team genereus genoeg om generatoren te ontwikkelen, zodat we dit gewoon konden doen;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return
for i in myRangeSmart(10):
    print i

Nu voert bij elke iteratie een functie op de generator genaamd next() de functie uit totdat deze ofwel een ‘yield’-statement bereikt waarin het stopt en de waarde ‘oplevert’ of het einde van de functie. In dit geval wordt bij de eerste aanroep next() uitgevoerd tot aan de opbrengstverklaring en opbrengst ‘n’, bij de volgende aanroep zal het de increment-instructie uitvoeren, terugspringen naar de ‘while’, evalueren het, en als het waar is, zal het stoppen en weer ‘n’ opleveren, het zal zo doorgaan totdat de while-voorwaarde false retourneert en de generator naar het einde van de functie springt.


Antwoord 26

Opbrengst is een object

Een return in een functie retourneert een enkele waarde.

Als je wilt dat een functie een enorme reeks waarden teruggeeft, gebruik dan yield.

Belangrijker is dat yield een barrière is.

zoals een barrière in de CUDA-taal, het zal de controle pas overdragen als het wordt
voltooid.

Dat wil zeggen, het zal de code in uw functie vanaf het begin uitvoeren totdat het yield bereikt. Dan zal het de eerste waarde van de lus teruggeven.

Vervolgens voert elke andere aanroep de lus die u in de functie hebt geschreven nog een keer uit, waarbij de volgende waarde wordt geretourneerd totdat er geen waarde meer is om te retourneren.


Antwoord 27

Veel mensen gebruiken return in plaats van yield, maar in sommige gevallen kan yield efficiënter en gemakkelijker om mee te werken zijn.

Hier is een voorbeeld waarvoor yield absoluut het beste is:

retour (in functie)

import random
def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

opbrengst (in functie)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Oproepfuncties

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)
dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Beide functies doen hetzelfde, maar yield gebruikt drie regels in plaats van vijf en heeft één variabele minder om je zorgen over te maken.

Dit is het resultaat van de code:

Uitvoer

Zoals je kunt zien, doen beide functies hetzelfde. Het enige verschil is dat return_dates() een lijst geeft en yield_dates() een generator geeft.

Een echt voorbeeld zou zoiets zijn als het regel voor regel lezen van een bestand of als je gewoon een generator wilt maken.


Antwoord 28

yield is als een retourelement voor een functie. Het verschil is dat het yield element een functie in een generator verandert. Een generator gedraagt ​​zich net als een functie totdat er iets wordt ‘opgebracht’. De generator stopt totdat hij de volgende keer wordt aangeroepen en gaat verder vanaf precies hetzelfde punt als waar hij begon. U kunt een reeks van alle ‘opbrengst’-waarden in één krijgen door list(generator()) aan te roepen.


Antwoord 29

Het zoekwoord yield verzamelt eenvoudig terugkerende resultaten. Denk aan yield zoals return +=


Antwoord 30

Nog een TL;DR

Iterator op lijst: next() geeft het volgende element van de lijst terug

Iteratorgenerator: next() berekent direct het volgende element (voer code uit)

Je kunt de opbrengst/generator zien als een manier om de controlestroom handmatig van buitenaf uit te voeren (zoals continue loop één stap), door next aan te roepen, hoe complex de stromen.

Opmerking: De generator is NIET een normale functie. Het onthoudt de vorige status als lokale variabelen (stack). Zie andere antwoorden of artikelen voor gedetailleerde uitleg. De generator kan slechts één keer worden geïtereerd. Je zou zonder yield kunnen, maar het zou niet zo mooi zijn, dus het kan worden beschouwd als ‘zeer mooie’ taalsuiker.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

five × 2 =

Other episodes