Ce face cuvântul cheie „yield”? (Programare, Python, Iterator, Generator, Randament, Coroutine)

Alex. S. a intrebat.

Care este utilizarea cuvântului yield în Python și ce face acesta?

De exemplu, încerc să înțeleg acest cod1:

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  

Și acesta este apelantul:

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

Ce se întâmplă atunci când metoda _get_child_candidates este apelată?Este returnată o listă? Un singur element? Este apelată din nou? Când se vor opri apelurile ulterioare?


1. Această bucată de cod a fost scrisă de Jochen Schulz (jrschulz), care a creat o bibliotecă Python excelentă pentru spații metrice. Acesta este linkul către sursa completă: Modul mspace.

Comentarii

  • @Booboo – ai dreptate – sistemul este stricat – votul nu le va elimina –  > Por NeilG.
  • @booboo: StackOverflow are adesea răspunsuri mult mai bune decât manualele. „Reputația” și „medaliile” aici sunt doar o grămadă de numere. Nu e ceva care să te deranjeze. Ceea ce este surprinzător, totuși, este cât de multă muncă neplătită fac moderatorii. Atât de mult timp personal pus la dispoziție. –  > Por Nav.
38 răspunsuri
e-satis

Pentru a înțelege ce yield face, trebuie să înțelegeți ce generatoare sunt. Și înainte de a înțelege generatoarele, trebuie să înțelegeți iterabilele.

Iterabile

Atunci când creați o listă, puteți citi elementele sale unul câte unul. Citirea elementelor sale unul câte unul se numește iterație:

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

mylist este un iterabilă. Atunci când utilizați o înțelegere de listă, creați o listă, deci un iterabil:

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

Tot ceea ce puteți utiliza „for... in...” este un iterabil; lists, , strings, , fișiere…

Aceste iterabile sunt la îndemână pentru că le puteți citi oricât de mult doriți, dar stocați toate valorile în memorie și acest lucru nu este întotdeauna ceea ce doriți atunci când aveți multe valori.

Generatoare

Generatorii sunt iteratori, un fel de iterabile pe care se poate itera doar o singură dată. Generatorii nu stochează toate valorile în memorie, ele generează valorile din mers.:

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

Este exact același lucru, cu excepția faptului că ați folosit () în loc de []. DAR, tu nu se poate efectua for i in mygenerator a doua oară, deoarece generatoarele nu pot fi utilizate decât o singură dată: ele calculează 0, apoi uită de el și calculează 1, iar la final calculează 4, unul câte unul.

Randament

yield este un cuvânt cheie care se utilizează ca și return, , cu excepția faptului că funcția va returna un generator.

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

Aici este un exemplu inutil, dar este la îndemână atunci când știți că funcția dvs. va returna un set uriaș de valori pe care nu va trebui să le citiți decât o singură dată.

Pentru a stăpâni yield, , trebuie să înțelegeți că atunci când apelați funcția, codul pe care l-ați scris în corpul funcției nu se execută. Funcția returnează doar obiectul generator, acest lucru este un pic mai complicat.

Apoi, codul dvs. va continua de unde a rămas de fiecare dată când for utilizează generatorul.

Acum partea dificilă:

Prima dată când for apelează obiectul generator creat din funcția dvs. va rula codul din funcția dvs. de la început până când va ajunge la yield, , apoi va returna prima valoare din buclă. Apoi, fiecare apel ulterior va rula o altă iterație a buclei pe care ați scris-o în funcție și va returna următoarea valoare. Acest lucru va continua până când generatorul este considerat gol, ceea ce se întâmplă atunci când funcția rulează fără să atingă yield. Acest lucru se poate datora faptului că bucla s-a încheiat sau pentru că nu mai satisfaceți o funcție "if/else".


Codul dvs. este explicat

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

Apelant:

# 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

Acest cod conține mai multe părți inteligente:

  • Bucla itera pe o listă, dar lista se extinde în timp ce bucla este iterată. Este o modalitate concisă de a trece prin toate aceste date imbricate, chiar dacă este puțin periculoasă, deoarece puteți ajunge la o buclă infinită. În acest caz, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) epuizează toate valorile generatorului, dar while continuă să creeze noi obiecte de generator care vor produce valori diferite de cele anterioare, deoarece nu este aplicat pe același nod.

  • extend() este o metodă de obiect listă care așteaptă un iterabil și adaugă valorile sale la listă.

De obicei, îi transmitem o listă:

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

Dar în codul dvs., aceasta primește un generator, ceea ce este bine pentru că:

  1. Nu este nevoie să citiți valorile de două ori.
  2. S-ar putea să aveți mulți copii și nu doriți să le stocați pe toate în memorie.

Și funcționează pentru că Python nu-i pasă dacă argumentul unei metode este o listă sau nu. Python se așteaptă la iterabile, așa că va funcționa cu șiruri, liste, tupluri și generatoare! Acest lucru se numește duck typing și este unul dintre motivele pentru care Python este atât de cool. Dar aceasta este o altă poveste, pentru o altă întrebare…

Vă puteți opri aici sau puteți citi puțin mai departe pentru a vedea o utilizare avansată a unui generator:

Controlul epuizării unui 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
...

Notă: Pentru Python 3, utilizațiprint(corner_street_atm.__next__()) sau print(next(corner_street_atm))

Poate fi util pentru diverse lucruri, cum ar fi controlul accesului la o resursă.

Itertools, cel mai bun prieten al tău

Modulul itertools conține funcții speciale pentru a manipula iterabile. Ați dorit vreodată să duplicați un generator?Să înlănțuiți două generatoare? Să grupați valori într-o listă imbricata cu un singur rând? Map / Zip fără a crea o altă listă?

Atunci doar import itertools.

Un exemplu? Să vedem posibilele ordine de sosire pentru o cursă de patru cai:

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

Înțelegerea mecanismelor interne ale iterației

Iterarea este un proces care implică iterabile (care implementează __iter__() ) și iteratori (care implementează metoda __next__() ).Iterabilele sunt orice obiecte din care se poate obține un iterator. Iteratorii sunt obiecte care vă permit să iterați pe iterabile.

Pentru mai multe informații, consultați acest articol despre cum se utilizează for funcționează buclele.

Comentarii

    455

  • yield nu este atât de magic pe cât sugerează acest răspuns. Atunci când apelați o funcție care conține un yield declarație oriunde, veți obține un obiect generator, dar nu se execută niciun cod. Apoi, de fiecare dată când extrageți un obiect din generator, Python execută codul din funcție până când ajunge la un yield declarație, apoi se oprește și livrează obiectul. Atunci când extrageți un alt obiect, Python reia imediat după ce a fost extrasă declarația yield și continuă până când ajunge la o altă yield (adesea același, dar o iterație mai târziu). Acest lucru continuă până când funcția se termină, moment în care generatorul este considerat epuizat. –  > Por Matthias Fripp.
  • 49

  • „Aceste iterabile sunt la îndemână… dar stochezi toate valorile în memorie și nu este întotdeauna ceea ce vrei”, este fie greșit, fie confuz. Un iterabil returnează un iterator la apelarea iter() asupra iterabilului, iar un iterator nu trebuie întotdeauna să își stocheze valorile în memorie, în funcție de implementarea iter acesta poate, de asemenea, să genereze valori în secvență la cerere. –  > Por picmate 涅.
  • Ar fi bine să se adauge la acest lucru mare răspuns de ce Este exact la fel, cu excepția faptului că ați folosit () în loc de [], , mai exact ce () este (poate exista o confuzie cu un tuple). –  > Por WoJ.
  • S-ar putea să mă înșel, dar un generator nu este un iterator, un „generator numit” este un iterator. –  > Por aderchox.
  • @MatthiasFripp „Acest lucru continuă până când funcția se execută la sfârșit” – sau întâlnește un return declarație. (return este permisă într-o funcție care conține yield, , cu condiția să nu specifice o valoare de returnare). –  > Por alani.
utilizator28409

Scurtătură pentru a înțelege yield

Atunci când vedeți o funcție cu yield declarații, aplicați acest truc simplu pentru a înțelege ce se va întâmpla:

  1. Introduceți o linie result = [] la începutul funcției.
  2. Înlocuiți fiecare yield expr cu result.append(expr).
  3. Introduceți o linie return result în partea de jos a funcției.
  4. Gata – nu mai este nevoie de yield declarații! Citiți și descifrați codul.
  5. Comparați funcția cu definiția originală.

Acest truc vă poate da o idee despre logica din spatele funcției, dar ceea ce se întâmplă de fapt cu yield este semnificativ diferit de ceea ce se întâmplă în abordarea bazată pe liste. În multe cazuri, abordarea prin randament va fi mult mai eficientă din punct de vedere al memoriei și, de asemenea, mai rapidă. În alte cazuri, acest truc vă va bloca într-o buclă infinită, chiar dacă funcția originală funcționează foarte bine. Citiți mai departe pentru a afla mai multe…

Nu vă confundați Iterables, Iterators și Generators

În primul rând, funcțiile protocolul iterator – atunci când scrieți

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

Python efectuează următorii doi pași:

  1. Obține un iterator pentru mylist:

    Apelarea iter(mylist) -> acest lucru returnează un obiect cu un next() metodă (sau __next__() în Python 3).

    Acesta este pasul despre care majoritatea oamenilor uită să vă spună.

  2. Folosește iteratorul pentru a face o buclă peste elemente:

    Continuă să apelezi la next() pe iteratorul returnat de la pasul 1. Valoarea returnată de la next() este atribuită la x și se execută corpul buclei. În cazul în care se produce o excepție StopIteration este ridicată din interiorul next(), înseamnă că nu mai există alte valori în iterator și că bucla este închisă.

Adevărul este că Python execută cei doi pași de mai sus ori de câte ori dorește să o facă să facă o buclă conținutul unui obiect – deci ar putea fi o buclă for, dar ar putea fi și un cod de tipul otherlist.extend(mylist) (unde otherlist este o listă Python).

Aici mylist este un iterabil deoarece implementează protocolul iterator. Într-o clasă definită de utilizator, puteți implementa protocolul __iter__() pentru a face ca instanțele clasei dvs. să fie iterabile. Această metodă ar trebui să returneze un iterator. Un iterator este un obiect cu un next() metodă. Este posibil să se implementeze atât __iter__() și next() în aceeași clasă și să se poată utiliza __iter__() return self. Acest lucru va funcționa în cazuri simple, dar nu și atunci când doriți ca doi iteratori să ruleze în buclă peste același obiect în același timp.

Acesta este protocolul iteratorilor, multe obiecte implementează acest protocol:

  1. Listele încorporate, dicționarele, tuplurile, seturile, fișierele.
  2. Clase definite de utilizator care implementează __iter__().
  3. Generatori.

Rețineți că un for nu știe cu ce tip de obiect are de-a face – urmează doar protocolul iteratorului și este fericit să primească element după element în timp ce apelează la next(). Listele încorporate își returnează elementele unul câte unul, dicționarele returnează elementele chei una câte una, fișierele returnează elementele liniile una câte una, etc. Iar generatoarele returnează… ei bine, aici se află yield intervine:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

În loc de yield dacă ați avea trei declarații return instrucțiuni în f123() doar prima ar fi executată, iar funcția ar ieși. Dar f123() nu este o funcție obișnuită. Când f123() este apelată, ea nu returnează niciuna dintre valorile din declarațiile yield! Ea returnează un obiect generator. De asemenea, funcția nu iese cu adevărat – intră într-o stare suspendată. Când funcția for încearcă să se bucleze peste obiectul generator, funcția își reia starea de suspendare chiar de la următoarea linie după yield de la care s-a întors anterior, execută următoarea linie de cod, în acest caz, un yield și îl returnează ca fiind următorul element. Acest lucru se întâmplă până când funcția iese, moment în care generatorul ridică StopIteration, , iar bucla iese din buclă.

Așadar, obiectul generator este un fel de adaptor – la un capăt, acesta prezintă protocolul iterator, expunând __iter__() și next() pentru a păstra for bucla să fie mulțumită. La celălalt capăt, însă, execută funcția doar atât cât să obțină următoarea valoare din ea și o pune din nou în modul suspendat.

De ce să folosiți generatoare?

De obicei, puteți scrie un cod care nu folosește generatoare, dar care implementează aceeași logică. O opțiune este să folosiți „trucul” listei temporare pe care l-am menționat mai devreme. Acest lucru nu va funcționa în toate cazurile, de exemplu, dacă aveți bucle infinite, sau poate utiliza ineficient memoria atunci când aveți o listă foarte lungă. Cealaltă abordare este de a implementa o nouă clasă iterabilă SomethingIter care păstrează starea în membrii instanței și care efectuează următorul pas logic în next() (sau __next__() în Python 3). În funcție de logică, codul din interiorul clasei next() poate ajunge să arate foarte complex și să fie predispus la erori. Generatoarele de aici oferă o soluție simplă și curată.

Comentarii

    26

  • „Când vedeți o funcție cu declarații yield, aplicați acest truc simplu pentru a înțelege ce se va întâmpla” Nu cumva acest lucru ignoră complet faptul că se poate send într-un generator, ceea ce reprezintă o mare parte din rostul generatoarelor? –  > Por DanielSank.
  • „ar putea fi o buclă for, dar ar putea fi, de asemenea, un cod precum otherlist.extend(mylist)” -> Acest lucru este incorect. extend() modifică lista pe loc și nu returnează un iterabil. Încercarea de a face o buclă peste otherlist.extend(mylist) va eșua cu o eroare TypeError deoarece extend() returnează implicit None, și nu se poate face o buclă peste None. –  > Por Pedro.
  • @pedro Ați înțeles greșit această propoziție. Înseamnă că python efectuează cei doi pași menționați pe mylist (și nu pe otherlist) atunci când execută otherlist.extend(mylist). –  > Por astăzi.
Jason Baker

Gândiți-vă la asta în felul următor:

Un iterator este doar un termen fantezist pentru un obiect care are un next() metodă. Deci, o funcție de randament ajunge să fie ceva de genul acesta:

Versiunea originală:

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

for i in some_function():
    print i

Aceasta este practic ceea ce face interpretul Python cu codul de mai sus:

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

Pentru mai multe informații despre ceea ce se întâmplă în spatele scenei, aplicația for bucla poate fi rescrisă astfel:

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

Asta are mai mult sens sau doar vă încurcă și mai mult? 🙂

Ar trebui să remarc faptul că acest este o simplificare excesivă în scop ilustrativ. 🙂

Comentarii

  • __getitem__ ar putea fi definit în loc de __iter__. De exemplu: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), , Se va imprima: 0, 10, 20, …, 90 –  > Por jfs.
  • 20

  • Am încercat acest exemplu în Python 3.6 și dacă creez iterator = some_function(), , variabila iterator nu are o funcție numită next() ci doar o funcție __next__() funcție. M-am gândit să menționez acest lucru. –  > Por Peter.
  • Unde se află for pe care ai scris-o tu apelează funcția __iter__ metoda de iterator, , instanța instanțiată a it? –  > Por SystematicDisintegration.
  • Din păcate, acest răspuns nu este deloc adevărat. Nu este ceea ce face interpretul python cu generatoarele. Acesta nu creează o clasă pornind de la funcția generator și nu implementează __iter__ și __next__. Ceea ce face de fapt sub capotă este explicat în această postare stackoverflow.com/questions/45723893/…. Pentru a-l cita pe @Raymond Hettinger „generatoarele nu sunt implementate intern așa cum se arată în clasa ta python pură. În schimb, ei împărtășesc cea mai mare parte a aceleiași logici ca și funcțiile obișnuite” –  > Por gioxc88.
ninjagecko

yield se reduce la două fapte simple:

  1. Dacă compilatorul detectează yield cuvântul cheie oriunde în interiorul unei funcții, funcția respectivă nu se mai întoarce prin intermediul funcției return nu mai revine. În schimb,, , ea imediat returnează un obiect „listă în așteptare” leneș numit generator
  2. Un generator este iterabil. Ce este un iterabil? Este ceva asemănător cu un list sau set sau range sau dict-view, cu un protocol încorporat pentru a vizita fiecare element într-o anumită ordine..

Pe scurt: un generator este o listă leneșă, care se execută în mod incremental, , și yield declarațiile vă permit să folosiți notația de funcție pentru a programa valorile listei pe care generatorul ar trebui să le scuipe în mod incremental.

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

Exemplu

Să definim o funcție makeRange care este la fel ca cea din Python range. Apelarea makeRange(n) RETURNEAZĂ UN 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>

Pentru a forța generatorul să returneze imediat valorile sale în așteptare, îl puteți trece în list() (la fel ca orice iterabil):

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

Comparând exemplul cu „returnând doar o listă”

Exemplul de mai sus poate fi considerat ca fiind o simplă creare a unei liste pe care o adăugați și o returnați:

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

Există totuși o diferență majoră; a se vedea ultima secțiune.


Cum ați putea utiliza generatoarele

Un iterabil este ultima parte a unei înțelegeri de listă, iar toți generatorii sunt iterabili, așa că sunt adesea utilizați astfel:

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

Pentru a vă familiariza mai bine cu generatorii, vă puteți juca cu itertools (asigurați-vă că utilizați chain.from_iterable în loc de chain atunci când este cazul). De exemplu, ați putea chiar să utilizați generatorii pentru a implementa liste leneșe infinit de lungi, cum ar fi itertools.count(). Ați putea implementa propriul dvs. def enumerate(iterable): zip(count(), iterable), sau, alternativ, puteți face acest lucru cu yield într-un while-loop.

Vă rugăm să rețineți: generatorii pot fi utilizați de fapt pentru mult mai multe lucruri, cum ar fi implementarea de corutine sau programarea nedeterministă sau alte lucruri elegante. Cu toate acestea, punctul de vedere al „listelor leneșe” pe care îl prezint aici este cea mai comună utilizare pe care o veți găsi.


În spatele scenei

Iată cum funcționează „protocolul de iterație Python”. Adică, ce se întâmplă atunci când faceți list(makeRange(5)). Aceasta este ceea ce am descris mai devreme ca fiind o „listă leneșă, incrementală”.

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

Funcția încorporată next() apelează pur și simplu obiectele .next() care face parte din „protocolul de iterație” și se găsește pe toți iteratorii. Puteți utiliza manual funcția next() (și alte părți ale protocolului de iterație) pentru a implementa lucruri fanteziste, de obicei în detrimentul lizibilității, așa că încercați să evitați să faceți asta…


Minutiae

În mod normal, cei mai mulți oameni nu ar fi interesați de următoarele distincții și probabil vor dori să nu mai citească aici.

În limbajul Python, un iterabil este orice obiect care „înțelege conceptul de buclă de căutare”, precum o listă [1,2,3], , iar un iterator este o instanță specifică a buclei for-loop solicitate, cum ar fi [1,2,3].__iter__(). A generator este exact la fel ca orice iterator, cu excepția modului în care a fost scris (cu sintaxa funcției).

Atunci când solicitați un iterator dintr-o listă, acesta creează un nou iterator. Cu toate acestea, atunci când solicitați un iterator de la un iterator (ceea ce se întâmplă foarte rar), acesta vă oferă doar o copie a sa.

Astfel, în cazul puțin probabil în care nu reușiți să faceți ceva de genul acesta…

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

… atunci amintiți-vă că un generator este un iterator; adică, este de unică folosință. Dacă doriți să îl reutilizați, trebuie să apelați myRange(...) din nou. Dacă aveți nevoie să utilizați rezultatul de două ori, convertiți rezultatul într-o listă și stocați-l într-o variabilă x = list(myRange(5)). Cei care trebuie neapărat să cloneze un generator (de exemplu, cei care fac o metaprogramare teribil de hacker) pot folosi itertools.tee dacă este absolut necesar, deoarece iteratorul copiabil Python PEP a fost amânată.

Aaron Hall

Ce înseamnă yield în Python?

Schiță de răspuns/rezumat

  • O funcție cu yield, , atunci când este apelată, returnează un Generator.
  • Generatorii sunt iteratori deoarece implementează funcția protocolul iterator, , astfel încât puteți itera peste ei.
  • Un generator poate fi, de asemenea, un trimis informații, , ceea ce îl face, din punct de vedere conceptual, un o corutină.
  • În Python 3, puteți delegați de la un generator la altul în ambele direcții cu yield from.
  • (Anexa critică câteva răspunsuri, inclusiv pe cel de sus, și discută utilizarea lui return într-un generator).

Generatoare:

yield este legală numai în interiorul unei definiții de funcție, iar includerea lui yield într-o definiție de funcție face ca aceasta să returneze un generator.

Ideea generatoarelor provine din alte limbaje (a se vedea nota de subsol 1) cu implementări diferite. În cazul generatorilor din Python, execuția codului este înghețată în punctul în care se produce randamentul. Atunci când generatorul este apelat (metodele sunt discutate mai jos), execuția se reia și apoi se blochează la următorul randament.

yield oferă o modalitate simplă de implementare a protocolului iteratorilor, , definit de următoarele două metode:__iter__ și next (Python 2) sau __next__ (Python 3). Ambele metode transformă un obiect într-un iterator pe care îl puteți verifica cu ajutorul metodei Iterator Abstract BaseClass din collections modul.

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

Tipul generator este un subtip al iteratorului:

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

Și, dacă este necesar, putem face o verificare de tip în felul următor:

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

O caracteristică a unui Iterator este că, odată epuizat, nu se mai poate reutiliza sau reseta:

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

Va trebui să creați un alt an dacă doriți să îi folosiți din nou funcționalitatea (a se vedea nota de subsol 2):

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

Se pot obține date prin programare, de exemplu:

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

Generatorul simplu de mai sus este, de asemenea, echivalent cu cel de mai jos – începând cu Python 3.3 (și nu este disponibil în Python 2), puteți utiliza yield from:

def func(an_iterable):
    yield from an_iterable

Cu toate acestea, yield from permite, de asemenea, delegarea către subgeneratori,care va fi explicată în următoarea secțiune privind delegarea cooperantă cu subcorutine.

Corutine:

yield formează o expresie care permite trimiterea de date în generator (a se vedea nota de subsol 3).

Iată un exemplu, luați aminte la received variabila, care va indica datele care sunt trimise în generator:

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)

În primul rând, trebuie să punem în coadă generatorul cu funcția builtin, next. Aceasta va apela funcția corespunzătoare next sau __next__ în funcție de versiunea de Python pe care o utilizați:

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

Și acum putem trimite date în generator. (Trimiterea None este același lucru cu apelarea next.) :

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

Delegare cooperativă către o subcorectă cu yield from

Acum, reamintim că yield from este disponibil în Python 3. Acest lucru ne permite să delegăm corutinele către o subcorutine:


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?

Și acum putem delega funcționalitatea către un subgenerator și poate fi utilizată de un generator la fel ca mai sus:

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

Acum simulați adăugarea a încă 1.000 în cont plus randamentul contului (60,0):

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

Puteți citi mai multe despre semantica exactă a yield from în PEP 380.

Alte metode: close și throw

Site-ul close ridică GeneratorExit în momentul în care execuția funcției a fost înghețată. Aceasta va fi apelată și de către __del__ astfel încât puteți pune orice cod de curățare în locul în care vă ocupați de GeneratorExit:

my_account.close()

Puteți, de asemenea, să aruncați o excepție care poate fi tratată în generator sau propagată către utilizator:

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

Ridică:

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

Concluzie

Cred că am acoperit toate aspectele următoarei întrebări:

Ce înseamnă yield în Python?

Se pare că yield face multe. Sunt sigur că aș putea adăuga exemple și mai amănunțite la aceasta. Dacă vreți mai multe sau dacă aveți o critică constructivă, anunțați-mă comentând mai jos.


Apendice:

Critica răspunsului de top/acceptat**.

  • Este confuz cu privire la ceea ce face un iterabil, , folosind doar o listă ca exemplu. A se vedea referințele mele de mai sus, dar pe scurt: un iterabil are un __iter__ care returnează o metodă iterator. Un iterator oferă un .next (Python 2 sau .__next__ (Python 3), care este apelată implicit de către for bucle până când aceasta ridică StopIteration, , iar odată ce o face, va continua să o facă.
  • Apoi folosește o expresie de generator pentru a descrie ce este un generator. Deoarece un generator este pur și simplu o modalitate convenabilă de a crea un iterator, , nu face decât să încurce lucrurile, iar noi încă nu am ajuns la yield parte.
  • În Controlul epuizării unui generator el apelează la .next când, în schimb, ar trebui să folosească funcția încorporată, next. Acesta ar fi un strat de indirecție adecvat, deoarece codul său nu funcționează în Python 3.
  • Itertools? Acest lucru nu era relevant pentru ceea ce yield face, deloc.
  • Nici o discuție despre metodele care yield oferă împreună cu noua funcționalitate yield from din Python 3. Răspunsul de top/acceptat este un răspuns foarte incomplet.

Critica răspunsului care sugerează yield într-o expresie sau înțelegere a generatorului.

Gramatica permite în prezent orice expresie într-o înțelegere de listă.

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

Deoarece yield este o expresie, a fost promovată de unii ca fiind interesantă utilizarea ei în comprehensiuni sau expresii generatoare – în ciuda faptului că nu se citează niciun caz de utilizare deosebit de bun.

Dezvoltatorii CPython de bază sunt discută deprecierea permisiunii sale.Iată o postare relevantă din lista de discuții:

Pe 30 ianuarie 2017 la 19:05, Brett Cannon a scris:

On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:

Sunt de acord cu oricare dintre cele două abordări. Lăsând lucrurile așa cum sunt în Python 3nu este bine, IMHO.

Votul meu este să fie o SyntaxError, deoarece nu obțineți ceea ce vă așteptați de la sintaxă.

Sunt de acord că acesta este un loc rezonabil pentru noi, deoarece orice cod care se bazează pe comportamentul actual este prea inteligent pentru a fi sustenabil.

Pentru a ajunge acolo, probabil că vom dori:

  • SyntaxWarning sau DeprecationWarning în 3.7.
  • Avertizare Py3k în 2.7.x
  • SyntaxError în 3.8

Noroc, Nick.

— Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia

În plus, există un problemă nerezolvată (10544) care pare să arate în direcția aceasta niciodată fi o idee bună (PyPy, o implementare Python scrisă în Python, ridică deja avertismente de sintaxă).

În concluzie, până când dezvoltatorii CPython ne vor spune altceva: Nu puneți yield într-o expresie sau înțelegere a unui generator.

Site-ul return într-o expresie de generator

În Python 2:

Într-o funcție generator, declarația return nu are voie să includă un element expression_list. În acest context, un simplu return indică faptul că generatorul a terminat și va provoca StopIteration să fie ridicată.

Un expression_list este practic orice număr de expresii separate prin virgule – în esență, în Python 2, puteți opri generatorul cu return, , dar nu puteți returna o valoare.

În Python 3:

Într-o funcție de generator, simbolul return indică faptul că generatorul a terminat și va determina ca StopIteration să fie ridicată. Valoarea returnată (dacă există) este utilizată ca argument pentru a construi StopIteration și devine StopIteration.value atribut.

Note de subsol

  1. Limbajele CLU, Sather și Icon au fost menționate în propunere pentru a introduce conceptul de generatoare în Python. Ideea generală este că o funcție poate menține starea internă și poate produce puncte de date intermediare la cererea utilizatorului. Acest lucru promitea să fie superioară din punct de vedere al performanțelor față de alte abordări, inclusiv threading-ul Python, , care nici măcar nu este disponibil pe unele sisteme.

  2. Acest lucru înseamnă, de exemplu, că range obiectele nu sunt Iterators, chiar dacă sunt iterabile, deoarece pot fi reutilizate. La fel ca și listele, obiectele lor __iter__ metodele returnează obiecte iterator.

yield a fost introdus inițial ca instrucțiune, ceea ce înseamnă că putea apărea doar la începutul unei linii într-un bloc de cod.Acum yield creează o expresie yield.https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmtAceastă modificare a fost propusă pentru a permite unui utilizator să trimită date în generator la fel cum le-ar putea primi. Pentru a trimite date, trebuie să le putem atribui la ceva, iar pentru aceasta, o expresie pur și simplu nu va funcționa.

Comentarii

  • Aaron, în secțiunea pe care ați descris-o yield from, , pe a doua linie aveți under_management = yield # must receive deposited value. Poți explica ce înseamnă că este egal cu yield? Se pare că obțineți o valoare, dar nu este clar de unde. –  > Por Ricardo Barros Lourenço.
  • @RicardoBarrosLourenço Am actualizat puțin comentariile acelui cod – pare mai clar acum? –  > Por Aaron Hall.
  • Frumos. S-a îmbunătățit acum (de la argumentul de trimitere). –  > Por Ricardo Barros Lourenço.
Douglas Mayle

yield este la fel ca return – returnează orice îi spui (ca generator). Diferența este că, data viitoare când apelați generatorul, execuția începe de la ultimul apel la yield instrucțiune. Spre deosebire de return, Cadrul stivei nu este curățat atunci când are loc un randament, însă controlul este transferat înapoi la apelant, astfel încât starea sa va fi reluată la următoarea apelare a funcției.

În cazul codului dumneavoastră, funcția get_child_candidates se comportă ca un iterator, astfel încât atunci când extindeți lista, aceasta adaugă câte un element pe rând la noua listă.

list.extend apelează un iterator până la epuizarea acestuia. În cazul exemplului de cod pe care l-ați postat, ar fi mult mai clar să se returneze un tuple și să se adauge la listă.

Comentarii

    112

  • Acest lucru este aproape, dar nu este corect. De fiecare dată când apelați o funcție cu o instrucțiune yield în ea, aceasta returnează un obiect generator nou-nouț. Doar atunci când apelați metoda .next() a acelui generator, execuția se reia după ultimul yield. –  > Por kurosch.
Claudiu

Mai este un lucru de menționat: o funcție care cedează nu trebuie de fapt să se termine. Am scris un cod de genul acesta:

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

Apoi îl pot folosi în alt cod de genul acesta:

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

Chiar ajută la simplificarea unor probleme și face unele lucruri mai ușor de lucrat.

Daniel

Pentru cei care preferă un exemplu minimal de lucru, meditați la această sesiune interactivă Python:

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

Bob Stein

TL;DR

În loc de asta:

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

faceți asta:

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

Ori de câte ori vă găsiți construind o listă de la zero, yield fiecare piesă în parte.

Acesta a fost primul meu moment „aha” cu randamentul.


yield este un zaharat mod de a spune

a construi o serie de lucruri

Același comportament:

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

Comportament diferit:

Randamentul este o singură trecere: se poate itera doar o singură dată. Atunci când o funcție are un randament în ea, o numim funcție generatoare. Iar un iterator este ceea ce returnează. Acești termeni sunt revelatori. Pierdem comoditatea unui container, dar câștigăm puterea unei serii care este calculată în funcție de necesități și care este arbitrar de lungă.

Randamentul este leneș, , amână calculul. O funcție cu un randament în ea nu se execută de fapt deloc atunci când o apelați. Ea returnează un obiect iterator care își amintește unde a rămas. De fiecare dată când apelați next() pe iterator (acest lucru se întâmplă într-o buclă de tip „for-loop”), execuția avansează până la următorul randament. return ridică StopIteration și încheie seria (acesta este finalul natural al unei bucle for-loop).

Randamentul este versatil. Datele nu trebuie să fie stocate toate la un loc, ci pot fi puse la dispoziție pe rând. Poate fi infinită.

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

Dacă aveți nevoie de mai multe treceri și seria nu este prea lungă, apelați pur și simplu list() pe ea:

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

O alegere strălucită a cuvântului yield pentru că ambele sensuri se aplică:

randament – a produce sau a furniza (ca în agricultură)

…furnizează următoarele date din serie.

randament – a ceda sau a renunța (ca în cazul puterii politice)

…renunță la execuția CPU până când iteratorul avansează.

RBansal

Yield vă oferă un 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

După cum puteți vedea, în primul caz foo ține întreaga listă în memorie deodată. Nu este o mare problemă pentru o listă cu 5 elemente, dar ce se întâmplă dacă doriți o listă de 5 milioane? Nu numai că acest lucru este un mare mâncător de memorie, dar costă și foarte mult timp pentru a fi construit în momentul în care funcția este apelată.

În al doilea caz, bar vă oferă doar un generator. Un generator este un iterabil – ceea ce înseamnă că îl puteți folosi într-o funcție for buclă, etc., dar fiecare valoare poate fi accesată o singură dată. De asemenea, toate valorile nu sunt stocate în memorie în același timp; obiectul generator „își amintește” unde se afla în buclă ultima dată când l-ați apelat – în acest fel, dacă utilizați un iterabil pentru a număra (să zicem) până la 50 de miliarde, nu trebuie să numărați până la 50 de miliarde deodată și să stocați cele 50 de miliarde de numere prin care să numărați.

Din nou, acesta este un exemplu destul de artificial, probabil că ați folosi itertools dacă ați dori cu adevărat să numărați până la 50 de miliarde. 🙂

Acesta este cel mai simplu caz de utilizare a generatoarelor. După cum ați spus, poate fi folosit pentru a scrie permutări eficiente, folosind yield pentru a împinge lucrurile în sus prin stiva de apeluri în loc să folosiți un fel de variabilă de stivă. Generatorii pot fi, de asemenea, utilizați pentru traversarea specializată a arborilor și pentru tot felul de alte lucruri.

Comentarii

  • Doar o notă – în Python 3, range returnează, de asemenea, un generator în loc de o listă, astfel încât veți vedea o idee similară, cu excepția faptului că __repr__/__str__ sunt suprapuse pentru a afișa un rezultat mai frumos, în acest caz range(1, 10, 2). –  > Por EsteNotALie..
Jon Skeet

Întoarce un generator. Nu sunt deosebit de familiarizat cu Python, dar cred că este același lucru ca și în cazul lui blocurile iterator din C# dacă sunteți familiarizați cu acestea.

Ideea cheie este că compilatorul/interpretorul/ceva face o șmecherie astfel încât, în ceea ce privește apelantul, acesta poate continua să apeleze next() și va continua să returneze valori – ca și cum metoda generatorului ar fi fost în pauză. Evident, nu puteți „pune pe pauză” o metodă, așa că compilatorul construiește o mașină de stare pentru a vă aminti unde vă aflați în prezent și cum arată variabilele locale etc. Acest lucru este mult mai ușor decât să scrieți singur un iterator.

aestrivex

Există un tip de răspuns care nu cred că a fost dat încă, printre numeroasele răspunsuri excelente care descriu cum să folosești generatoarele. Aici este răspunsul la teoria limbajului de programare:

The yield din Python returnează un generator. Un generator în Python este o funcție care returnează continuări (și, în special, un tip de corutină, dar continuările reprezintă un mecanism mai general pentru a înțelege ce se întâmplă).

În teoria limbajelor de programare, continuările reprezintă un tip de calcul mult mai fundamental, dar nu sunt utilizate prea des, deoarece sunt extrem de greu de argumentat și, de asemenea, foarte greu de implementat. Dar ideea de continuare este simplă: este starea unui calcul care nu s-a terminat încă. În această stare, sunt salvate valorile curente ale variabilelor, operațiile care nu au fost încă efectuate și așa mai departe. Apoi, la un moment dat, mai târziu în program, continuarea poate fi invocată, astfel încât variabilele programului să fie readuse în starea respectivă și să fie efectuate operațiile care au fost salvate.

Continuările, în această formă mai generală, pot fi implementate în două moduri. În cadrul call/cc mod, stiva programului este literalmente salvată și apoi, atunci când este invocată continuarea, stiva este restaurată.

În stilul de transmitere a continuării (CPS), continuările sunt doar funcții normale (numai în limbajele în care funcțiile sunt de primă clasă) pe care programatorul le gestionează în mod explicit și le transmite subrutine. În acest stil, starea programului este reprezentată de închideri (și de variabilele care se întâmplă să fie codificate în acestea) mai degrabă decât de variabile care se află undeva pe stivă. Funcțiile care gestionează fluxul de control acceptă continuări ca argumente (în unele variante de CPS, funcțiile pot accepta mai multe continuări) și manipulează fluxul de control prin invocarea lor prin simpla lor apelare și întoarcere ulterioară. Un exemplu foarte simplu de stil de trecere a continuității este următorul:

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)

În acest exemplu (foarte simplist), programatorul salvează operațiunea de scriere efectivă a fișierului într-o continuare (care poate fi, potențial, o operațiune foarte complexă, cu multe detalii de scris) și apoi transmite acea continuare (adică, ca o închidere de primă clasă) unui alt operator care face mai multă procesare și apoi o apelează, dacă este necesar. (Folosesc mult acest model de proiectare în programarea GUI reală, fie pentru că îmi economisește linii de cod, fie, mai important, pentru a gestiona fluxul de control după declanșarea evenimentelor GUI).

Restul acestei postări va conceptualiza, fără a pierde din generalitate, continuările ca CPS, deoarece este al naibii de ușor de înțeles și de citit.

Acum să vorbim despre generatoare în Python. Generatorii sunt un subtip specific de continuare. În timp ce continuările sunt capabile, în general, să salveze starea unei calcul (adică, stiva de apeluri a programului), generatoarele sunt capabile să salveze doar starea iterației asupra unui iterator. Cu toate acestea, această definiție este ușor înșelătoare pentru anumite cazuri de utilizare a generatoarelor. De exemplu:

def f():
  while True:
    yield 4

Acesta este în mod clar un iterabil rezonabil, al cărui comportament este bine definit – de fiecare dată când generatorul itera peste el, acesta returnează 4 (și face acest lucru pentru totdeauna). Dar probabil că nu este tipul prototipic de iterabil care ne vine în minte atunci când ne gândim la iteratori (de ex, for x in collection: do_something(x)). Acest exemplu ilustrează puterea generatoarelor: dacă ceva este un iterator, un generator poate salva starea iterației sale.

Pentru a reitera: Continuările pot salva starea stivei unui program, iar generatorii pot salva starea iterației. Acest lucru înseamnă că continuările sunt mult mai puternice decât generatoarele, dar și că generatoarele sunt mult, mult mai ușoare. Sunt mai ușor de implementat pentru proiectantul limbajului și sunt mai ușor de utilizat pentru programator (dacă aveți ceva timp liber, încercați să citiți și să înțelegeți această pagină despre continuări și call/cc).

Dar ați putea cu ușurință să implementați (și să conceptualizați) generatoarele ca un caz simplu și specific al stilului de trecere a continuităților:

Ori de câte ori yield este apelată, aceasta îi spune funcției să returneze o continuare. Când funcția este apelată din nou, aceasta începe de unde a rămas. Astfel, în pseudo-pseudocod (adică nu pseudocod, dar nici cod), generatorul next este, în principiu, următoarea:

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

unde yield este de fapt un zahăr sintactic pentru funcția reală a generatorului, practic ceva de genul:

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

Rețineți că acesta este doar pseudocod și că implementarea reală a generatoarelor în Python este mai complexă. Dar, ca un exercițiu pentru a înțelege ce se întâmplă, încercați să utilizați stilul de trecere a continuității pentru a implementa obiectele generatorului fără a utiliza funcția yield cuvântul cheie.

tzot

Iată un exemplu în limbaj simplu. Voi oferi o corespondență între conceptele umane de nivel înalt și conceptele Python de nivel scăzut.

Vreau să operez asupra unei secvențe de numere, dar nu vreau să mă deranjez cu crearea acelei secvențe, ci vreau să mă concentrez doar pe operația pe care vreau să o fac. Așadar, fac următoarele:

  • Vă sun și vă spun că vreau o secvență de numere care să fie produsă într-un anumit mod și vă anunț care este algoritmul.
    Această etapă corespunde definarea funcției generatoare, adică funcția care conține o yield.
  • Ceva mai târziu, vă spun: „OK, pregătește-te să-mi spui secvența de numere”.
    Această etapă corespunde apelării funcției generator care returnează un obiect generator. Rețineți că nu-mi spuneți încă niciun număr; doar luați hârtia și creionul.
  • Eu vă întreb „spuneți-mi următorul număr”, iar dumneavoastră îmi spuneți primul număr; după aceea, așteptați să vă cer următorul număr. Este treaba ta să îți amintești unde ai fost, ce numere ai spus deja și care este următorul număr. Nu mă interesează detaliile.
    Această etapă corespunde apelării .next() la obiectul generator.
  • … repetați pasul anterior, până când…
  • în cele din urmă, s-ar putea să ajungeți la un final. Nu-mi spuneți un număr; doar strigați: „Stați pe loc! Am terminat! Gata cu numerele!”
    Acest pas corespunde obiectului generator care își termină treaba și ridică un StopIteration excepție Funcția generatoare nu trebuie să ridice excepția. Aceasta este ridicată automat atunci când funcția se termină sau emite o return.

Aceasta este ceea ce face un generator (o funcție care conține o funcție yield); începe să se execute, se oprește ori de câte ori face o yield, , iar atunci când i se cere un .next() valoare, el continuă din punctul în care a fost ultima dată. Se potrivește perfect, prin concepție, cu protocolul iterator din Python, care descrie modul de solicitare secvențială a valorilor.

Cel mai cunoscut utilizator al protocolului iterator este aplicația for din Python. Așadar, ori de câte ori faceți un:

for item in sequence:

nu contează dacă sequence este o listă, un șir de caractere, un dicționar sau un generator obiect așa cum am descris mai sus; rezultatul este același: citiți elementele dintr-o secvență, unul câte unul.

Rețineți că definând o funcție care conține un yield nu este singurul mod de a crea un generator; este doar cel mai simplu mod de a crea unul.

Pentru informații mai precise, citiți despre tipuri de iteratori, , pagina de declarația yield și generatori în documentația Python.

Mike McKerns

În timp ce o mulțime de răspunsuri arată de ce ați folosi un yield pentru a crea un generator, există mai multe utilizări pentru yield. Este destul de ușor să creezi o cortină, care permite trecerea de informații între două blocuri de cod. Nu voi repeta niciunul dintre exemplele frumoase care au fost deja date despre utilizarea lui yield pentru a crea un generator.

Pentru a vă ajuta să înțelegeți mai bine ce înseamnă un yield face în codul următor, puteți folosi degetul pentru a urmări ciclul prin orice cod care are un yield. De fiecare dată când degetul dvs. lovește butonul yield, , trebuie să așteptați pentru a next sau a send să fie introdus. Atunci când a next este apelată, urmăriți codul până când atingeți tasta yield… codul din partea dreaptă a yield este evaluat și returnat celui care l-a apelat… apoi se așteaptă. Când next este apelat din nou, se execută o altă buclă prin cod. Cu toate acestea, veți observa că într-o cortină, yield poate fi utilizat și cu un send… care va trimite o valoare de la apelant în funcția care produce randamentul. În cazul în care un send este dată, atunci yield primește valoarea trimisă și o scuipă pe partea stângă… apoi urmărirea prin cod progresează până când se ajunge la funcția yield din nou (returnând valoarea la sfârșit, ca și cum next a fost apelată).

De exemplu:

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

Comentarii

  • Drăguț! A trambulină (în sensul Lisp). Nu se văd prea des așa ceva! –  > Por 00prometheus.
Sławomir Lenart

Mai există o altă yield utilizare și semnificație (începând cu Python 3.3):

yield from <expr>

De la PEP 380 — Sintaxa pentru delegarea către un subgenerator:

Se propune o sintaxă pentru ca un generator să delege o parte din operațiunile sale unui alt generator. Acest lucru permite ca o secțiune de cod care conține „yield” să fie eliminată și plasată într-un alt generator. În plus, subgeneratorului i se permite să se întoarcă cu o valoare, iar valoarea este pusă la dispoziția generatorului care deleagă.

Noua sintaxă deschide, de asemenea, unele oportunități de optimizare atunci când un generator returnează valorile produse de un alt generator.

În plus, acest va introduce (începând cu Python 3.5):

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

pentru a evita confundarea corutinelor cu un generator obișnuit (astăzi yield este utilizat în ambele).

AbstProcDo

Toate răspunsurile sunt excelente, totuși un pic dificil pentru începători.

Presupun că ați învățat return declarația.

Ca o analogie, return și yield sunt gemeni. return înseamnă „a se întoarce și a se opri”, în timp ce „yield` înseamnă „a se întoarce, dar a continua”.

  1. Încercați să obțineți o listă num_list cu return.
def num_list(n):
    for i in range(n):
        return i

Executați-l:

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

Vedeți, veți obține un singur număr, în loc de o listă de numere. return nu vă permite niciodată să prevaleze în mod fericit, doar implementează o dată și renunță.

  1. Apare yield

Înlocuiește return cu 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]

Acum, câștigi pentru a obține toate numerele.

Comparând cu return care rulează o singură dată și se oprește, yield rulează de câte ori ai planificat.Poți interpreta return ca return one of them, , și yield ca return all of them. Acest lucru se numește iterable.

  1. Încă un pas și putem rescrie yield cu 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]

Este vorba de nucleul despre yield.

Diferența dintre o listă return de ieșire și obiectul yield de ieșire este:

Veți obține întotdeauna [0, 1, 2] dintr-o listă de obiecte, dar nu le veți putea prelua decât din „obiectul”. yield ieșire” o singură dată. Prin urmare, are un nou nume generator obiect, așa cum este afișat în Out[11]: <generator object num_list at 0x10327c990>.

În concluzie, ca o metaforă pentru a o înțelege:

  • return și yield sunt gemeni
  • list și generator sunt gemeni

Comentarii

  • Acest lucru este de înțeles, dar o diferență majoră este că puteți avea mai multe randamente într-o funcție/metodă. Analogia se întrerupe complet în acest punct. Randamentul își amintește locul într-o funcție, astfel încât, data viitoare când apelați next(), funcția dvs. continuă cu următoarea funcție. yield. Cred că acest lucru este important și ar trebui să fie exprimat. –  > Por Mike S.
alinsoar

Din punct de vedere al programării, iteratorii sunt implementați ca thunks.

Pentru a implementa iteratorii, generatorii și grupurile de fire pentru execuție simultană etc. ca thunks, se utilizează mesaje trimise către un obiect de închidere, , care are un dispecerat, iar dispecerul răspunde la „mesaje”.

next este un mesaj trimis către o închidere, creată de către „iter„.

Există o mulțime de moduri de a implementa acest calcul. Eu am folosit mutația, dar este posibil să se facă acest tip de calcul fără mutație, returnând valoarea curentă și următorul randament (ceea ce îl face pe transparență referențială). Racket utilizează o secvență de transformări ale programului inițial în unele limbaje intermediare, una dintre aceste rescrieri făcând ca operatorul de randament să fie transformat într-un limbaj cu operatori mai simpli.

Iată o demonstrație a modului în care ar putea fi rescris yield, care utilizează structura R6RS, dar semantica este identică cu cea din Python. Este vorba de același model de calcul și este necesară doar o modificare a sintaxei pentru a-l rescrie folosind yield din 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
->

Dustin Getz

Iată câteva exemple Python despre cum se implementează efectiv generatoarele ca și cum Python nu ar oferi zahăr sintactic pentru ele:

Ca un generator Python:

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

Folosind închideri lexicale în loc de generatoare

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)

Utilizarea închiderilor de obiecte în loc de generatoare (deoarece ClosuresAndObjectsAreAreEquivalent)

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)

johnzachary

Aveam de gând să postez „citește pagina 19 din cartea lui Beazley ‘Python: Essential Reference’ pentru o descriere rapidă a generatoarelor”, dar atât de mulți alții au postat deja descrieri bune.

De asemenea, rețineți că yield pot fi utilizate în corutine ca dual al utilizării lor în funcțiile generatoare. Deși nu este aceeași utilizare ca în cazul fragmentului dvs. de cod, (yield) poate fi utilizat ca expresie într-o funcție. Atunci când un apelant trimite o valoare metodei folosind expresia send() metoda, atunci corutina se va executa până la următoarea (yield) instrucțiune este întâlnită.

Generatoarele și corutinele sunt o modalitate interesantă de a configura aplicații de tip flux de date. M-am gândit că ar fi util să știți despre cealaltă utilizare a yield în funcții.

Engin OZTURK

Iată un exemplu simplu:

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)

Output:

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

Nu sunt un dezvoltator Python, dar mi se pare că yield ține poziția fluxului programului și următoarea buclă începe din poziția „yield”. Se pare că așteaptă la acea poziție și, chiar înainte de aceasta, returnează o valoare în afara, iar data viitoare continuă să lucreze.

Pare a fi o abilitate interesantă și plăcută 😀

Comentarii

  • Aveți dreptate. Dar care este efectul asupra fluxului care este de a vedea comportamentul „yield” ? Pot să schimb algoritmul în numele matematicii. Va ajuta la obținerea unei evaluări diferite a „randamentului” ? –  > Por Engin OZTURK.
Evgeni Sergeev

Iată o imagine mentală a ceea ce yield face.

Îmi place să mă gândesc la un fir de execuție ca având o stivă (chiar și atunci când nu este implementat în acest fel).

Atunci când este apelată o funcție normală, aceasta își pune variabilele locale pe stivă, face niște calcule, apoi șterge stiva și se întoarce. Valorile variabilelor sale locale nu mai sunt văzute niciodată.

Cu o funcție yield atunci când codul său începe să ruleze (adică după ce funcția este apelată, returnând un obiect generator, al cărui obiect next() este apoi invocată), aceasta își plasează în mod similar variabilele locale pe stivă și efectuează calcule pentru o perioadă de timp. Dar apoi, atunci când atinge punctul yield înainte de a șterge partea sa din stivă și de a se întoarce, aceasta ia o imagine instantanee a variabilelor sale locale și le stochează în obiectul generator. De asemenea, acesta notează locul în care se află în prezent în codul său (și anume, punctul specific yield declarație).

Deci este un fel de funcție înghețată de care se agață generatorul.

Când next() este apelat ulterior, acesta recuperează lucrurile care aparțin funcției pe stivă și o reanimează. Funcția continuă să calculeze de unde a rămas, fără să țină cont de faptul că tocmai a petrecut o veșnicie în depozitul rece.

Comparați următoarele exemple:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Atunci când apelăm a doua funcție, aceasta se comportă foarte diferit de prima. yield ar putea fi inaccesibilă, dar dacă este prezentă oriunde, schimbă natura lucrurilor cu care avem de-a face.

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

Apelarea yielderFunction() nu îi execută codul, ci face din cod un generator. (Poate că este o idee bună să numim astfel de lucruri cu yielder prefix pentru ușurința de citire).

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

La gi_code și gi_frame sunt câmpurile în care este stocată starea înghețată. Explorarea lor cu dir(..), , putem confirma că modelul nostru mental de mai sus este credibil.

Gavriel Cohen

Un exemplu simplu pentru a înțelege despre ce este vorba: yield

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


for i in f123():
    print (i)

Ieșirea este:

1 2 1 2 1 2 1 2

Comentarii

  • Nu ar fi tipărit doar pe o singură linie dacă ați fi rulat instrucțiunea print folosind print(i, end=' ')? Altfel, cred că comportamentul implicit ar pune fiecare număr pe o linie nouă –  > Por user9074332.
  • @user9074332, Aveți dreptate, dar este scris pe o singură linie pentru a facilita înțelegerea –  > Por Gavriel Cohen.
Mangu Singh Rajpurohit

Așa cum sugerează fiecare răspuns, yield este utilizat pentru crearea unui generator de secvențe. Este folosit pentru generarea unor secvențe în mod dinamic. De exemplu, în timp ce citiți un fișier linie cu linie pe o rețea, puteți utiliza funcția yield funcție după cum urmează:

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

Puteți să o utilizați în codul dvs. după cum urmează:

for line in getNextLines():
    doSomeThing(line)

Execution Control Transfer gotcha

Controlul execuției va fi transferat de la getNextLines() la funcția for buclă atunci când se execută yield. Astfel, de fiecare dată când getNextLines() este invocată, execuția începe din punctul în care a fost întreruptă ultima dată.

Astfel, pe scurt, o funcție cu următorul cod

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

for i in simpleYield():
    print i

va imprima

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

smwikipedia

(Răspunsul meu de mai jos vorbește doar din perspectiva utilizării generatorului Python, nu și a implementării subiacente a mecanismului generatorului, care implică unele trucuri de manipulare a stivei și a heap-ului).

Atunci când yield este utilizat în loc de return într-o funcție python, funcția respectivă este transformată în ceva special numit generator function. Funcția respectivă va returna un obiect de tip generator tip. Adresa yield este un indicator care notifică compilatorul python să trateze această funcție în mod special. Funcțiile normale se vor încheia odată ce o anumită valoare este returnată de la aceasta. Dar, cu ajutorul compilatorului, funcția generatoare poate fi considerată ca fiind ca fiind reluabilă. Adică, contextul de execuție va fi restabilit și execuția va continua de la ultima execuție. Până la apelarea explicită a funcției return, care va ridica un apel de tip StopIteration excepție (care face parte, de asemenea, din protocolul iteratorului), sau dacă ajungeți la sfârșitul funcției. Am găsit o mulțime de referințe despre generator dar aceasta una de la functional programming perspective este cea mai digerabilă.

(Acum vreau să vorbesc despre raționamentul din spatele generator, , și despre iterator pe baza propriei mele înțelegeri. Sper că acest lucru vă poate ajuta să înțelegeți motivația esențială a iteratorului și a generatorului. Un astfel de concept apare și în alte limbaje, cum ar fi C#).

Din câte am înțeles, atunci când dorim să procesăm o mulțime de date, de obicei, mai întâi stocăm datele undeva și apoi le procesăm una câte una. Dar acest naivă abordare este problematică. Dacă volumul de date este uriaș, este costisitor să le stocăm în prealabil ca un întreg. Așadar, în loc să stocăm data în sine în mod direct, de ce să nu stocăm un fel de metadata indirect, adică the logic how the data is computed.

Există 2 abordări pentru a înfășura astfel de metadate.

  1. Abordarea OO, în care înfășurăm metadatele as a class. Aceasta este așa-numita iterator care implementează protocolul iterator (adică protocolul __next__(), , și __iter__() metode). Acesta este, de asemenea, cel mai des întâlnit modelul de proiectare al iteratorilor.
  2. Abordarea funcțională, înfășurăm metadatele as a function. Acesta este așa-numitul generator function. Dar, sub capotă, datele returnate generator object este încă returnat IS-A iterator, deoarece implementează și protocolul iterator.

În orice caz, este creat un iterator, adică un obiect care vă poate oferi datele dorite. Abordarea OO poate fi un pic mai complexă. Oricum, care dintre ele să o folosiți depinde de dumneavoastră.

redbandit

În concluzie, metoda yield transformă funcția dvs. într-o fabrică care produce un obiect special numit generator care se înfășoară în jurul corpului funcției dvs. originale. Atunci când generator este iterată, aceasta execută funcția dvs. până când ajunge la următoarea yield apoi suspendă execuția și o evaluează la valoarea transmisă către yield. Se repetă acest proces la fiecare iterație până când calea de execuție iese din funcție. De exemplu,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

emite pur și simplu

one
two
three

Puterea provine din utilizarea generatorului cu o buclă care calculează o secvență, generatorul execută bucla oprindu-se de fiecare dată pentru a „ceda” următorul rezultat al calculului, în acest fel el calculează o listă din mers, avantajul fiind memoria economisită pentru calculele deosebit de mari

Să presupunem că doriți să creați un generator propriu range care să producă un interval iterabil de numere, ați putea proceda în felul următor,

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

și să o folosiți astfel;

for i in myRangeNaive(10):
    print i

Dar acest lucru este ineficient deoarece

  • creați o matrice pe care o utilizați o singură dată (ceea ce înseamnă risipă de memorie)
  • Acest cod face, de fapt, o buclă de două ori peste acel array! 🙁

Din fericire, Guido și echipa sa au fost suficient de generoși pentru a dezvolta generatoare astfel încât să putem face doar acest lucru;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

Acum, la fiecare iterație, o funcție de pe generator numită next() execută funcția până când fie ajunge la o instrucțiune „yield” în care se oprește și „cedează” valoarea, fie ajunge la sfârșitul funcției. În acest caz, la primul apel, next() se execută până la declarația yield și produce „n”, la următorul apel va executa declarația de incrementare, va sări înapoi la „while”, o va evalua și, dacă este adevărată, se va opri și va produce din nou „n”, continuând astfel până când condiția while revine la fals și generatorul sare la sfârșitul funcției.

Kaleem Ullah

Yield este un obiect

A return dintr-o funcție va returna o singură valoare.

Dacă se dorește ca o funcție să returneze un set imens de valori, , utilizați yield.

Și mai important, yield este un barieră.

ca și bariera din limbajul CUDA, nu va transfera controlul până când nu se finalizează.

Adică, va rula codul din funcția dvs. de la început până când va ajunge la yield. Apoi, va returna prima valoare din buclă.

Apoi, fiecare alt apel va rula bucla pe care ați scris-o în funcție încă o dată, returnând următoarea valoare până când nu mai există nicio valoare de returnat.

Tom Fuller

Mulți oameni folosesc return în loc de yield, , dar în unele cazuri yield poate fi mai eficient și mai ușor de utilizat.

Iată un exemplu care yield este cu siguranță cel mai bun pentru:

return (în funcție)

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

randament (în funcție)

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.

Apelarea funcțiilor

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)

Ambele funcții fac același lucru, dar yield utilizează trei linii în loc de cinci și are o variabilă mai puțin de care trebuie să se preocupe.

Acesta este rezultatul din cod:

După cum puteți vedea, ambele funcții fac același lucru. Singura diferență este return_dates() oferă o listă, iar yield_dates() dă un generator.

Un exemplu din viața reală ar fi ceva de genul citirii unui fișier linie cu linie sau dacă doriți doar să creați un generator.

Will Dereham

yield este ca un element de întoarcere pentru o funcție. Diferența constă în faptul că yield element transformă o funcție într-un generator. Un generator se comportă la fel ca o funcție până când ceva este „cedat”. Generatorul se oprește până la următorul apel și continuă exact din același punct din care a început. Puteți obține o secvență a tuturor valorilor „cedate” într-o singură secvență, prin apelarea funcției list(generator()).

Bahtiyar Özdere

yield colectează pur și simplu rezultatele returnate. Gândiți-vă la yield ca la return +=

Dimitris Fasarakis Hilliard

Iată un simplu yield abordare simplă, pentru a calcula seria fibonacci, explicată:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Când introduceți acest lucru în REPL și apoi încercați să îl apelați, veți obține un rezultat mistificator:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

Acest lucru se datorează faptului că prezența lui yield a semnalat lui Python că doriți să creați un fișier generator, , adică un obiect care generează valori la cerere.

Așadar, cum generați aceste valori? Acest lucru se poate face fie direct, utilizând funcția încorporată next, , fie, indirect, alimentând o construcție care consumă valori.

Utilizarea funcției încorporate next() se invocă direct funcția .next/__next__, forțând generatorul să producă o valoare:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Indirect, dacă furnizați fib la un for bucla, o list inițializator, un tuple inițializator sau orice altceva care așteaptă un obiect care generează/produce valori, veți „consuma” generatorul până când acesta nu mai poate produce nicio valoare (și se întoarce):

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

În mod similar, cu un tuple inițializator:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Un generator diferă de o funcție în sensul că este leneș. El realizează acest lucru prin menținerea stării sale locale și prin faptul că vă permite să o reluați ori de câte ori aveți nevoie.

Atunci când se invocă pentru prima dată fib prin apelarea acestuia:

f = fib()

Python compilează funcția, întâlnește yield cuvântul cheie și pur și simplu vă returnează un obiect generator. Se pare că nu este foarte util.

Atunci când îi solicitați apoi generează prima valoare, direct sau indirect, execută toate instrucțiunile pe care le găsește, până când întâlnește un yield, , apoi returnează valoarea pe care ați furnizat-o la yield și se oprește. Pentru un exemplu care să demonstreze mai bine acest lucru, să folosim niște print apeluri (înlocuiți cu print "text" if pe Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Acum, intrați în REPL:

>>> gen = yielder("Hello, yield!")

aveți acum un obiect generator care așteaptă o comandă pentru ca acesta să genereze o valoare. Utilizați next și vedeți ce se tipărește:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Rezultatele necotate sunt ceea ce se tipărește. Rezultatul citat este ceea ce este returnat de la yield. Apelați la next din nou acum:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Generatorul își amintește că a fost întrerupt la yield value și își reia activitatea de acolo. Următorul mesaj este tipărit, iar căutarea pentru yield declarație la care să se facă pauză se efectuează din nou (datorită while buclă).

Christophe Roussy

Încă un TL;DR

Iterator pe listă: next() returnează următorul element al listei

Generator de iteratori: next() va calcula următorul element din mers (execută codul)

Puteți vedea yield/generator ca pe o modalitate de a executa manual fluxul de control din exterior (cum ar fi continuarea buclei cu un pas), prin apelarea next, , oricât de complex ar fi fluxul.

Notă:: Generatorul este NU ESTE o funcție normală. Acesta își amintește starea anterioară ca și variabilele locale (stivă). Consultați alte răspunsuri sau articole pentru explicații detaliate. Generatorul poate fi doar iterat o singură dată. Ați putea face fără yield, , dar nu ar fi la fel de frumos, așa că poate fi considerat un zahăr lingvistic „foarte frumos”.

blueray

yield este similar cu return. Diferența este următoarea:

yield face ca o funcție să fie iterabilă (în următorul exemplu primes(n = 1) funcția devine iterabilă).
Ceea ce înseamnă, în esență, că data viitoare când funcția este apelată, aceasta va continua de unde a rămas (adică după linia de yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

În exemplul de mai sus, dacă isprime(n) este adevărat, aceasta va returna numărul prim. În următoarea iterație, va continua de la următoarea linie

n += 1  

Rafael

O analogie ar putea ajuta la înțelegerea ideii de aici:

Imaginați-vă că ați creat o mașinărie uimitoare, capabilă să producă mii și mii de becuri pe zi. Mașina generează aceste becuri în cutii cu un număr de serie unic. Nu aveți suficient spațiu pentru a stoca toate aceste becuri în același timp (adică nu puteți ține pasul cu viteza mașinii din cauza limitării stocării), așa că ați dori să ajustați această mașină pentru a genera becuri la cerere.

Generatoarele Python nu diferă prea mult de acest concept.

Imaginați-vă că aveți o funcție x care generează numere de serie unice pentru cutii. Evident, puteți avea un număr foarte mare de astfel de coduri de bare generate de funcție. O opțiune mai înțeleaptă, și eficientă din punct de vedere al spațiului, este să generați aceste numere de serie la cerere.

Codul mașinii:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

După cum puteți vedea, avem o „funcție” autonomă pentru a genera de fiecare dată următorul număr de serie unic. Această funcție returnează înapoi un generator! După cum puteți vedea, nu apelăm funcția de fiecare dată când avem nevoie de un nou număr de serie, ci folosim next() generatorul pentru a obține următorul număr de serie.

Ieșire:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n

thavan

yield produce ceva. Este ca și cum cineva ți-ar cere să faci 5 prăjituri. Dacă ați terminat cel puțin o prăjitură, i-o puteți da să o mănânce în timp ce faceți alte prăjituri.

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Aici factory se numește un generator, care îți face prăjituri. Dacă apelați make_function, , obțineți un generator în loc să executați această funcție. Aceasta se datorează faptului că atunci când yield cuvântul cheie este prezent într-o funcție, aceasta devine un generator.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Au consumat toate prăjiturile, dar cer din nou una.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

Și li se spune să nu mai ceară altele. Deci, odată ce ați consumat un generator, ați terminat cu el. Trebuie să apelați make_cake din nou dacă vrei mai multe prăjituri. Este ca și cum ai face o altă comandă de prăjituri.

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

De asemenea, puteți folosi for loop cu un generator ca cel de mai sus.

Încă un exemplu: Să spunem că doriți o parolă aleatorie de fiecare dată când o cereți.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

Iată rpg este un generator, care poate genera un număr infinit de parole aleatoare. Deci putem spune, de asemenea, că generatoarele sunt utile atunci când nu cunoaștem lungimea secvenței, spre deosebire de listă, care are un număr finit de elemente.

Andy

În Python generators (un tip special de iterators) sunt utilizate pentru a genera serii de valori, iar yield este la fel ca și cuvântul cheie return din funcțiile generatoare.

Celălalt lucru fascinant yield pe care îl face cuvântul cheie este salvarea valorilor state a unei funcții generatoare.

Astfel, putem seta un number la o valoare diferită de fiecare dată când funcția generator dă randament.

Iată un exemplu:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
    primeGenerator = getPrimes(base)
    primeGenerator.send(None)
    for power in range(iterations):
        print(primeGenerator.send(base ** power))

Chen A.

Toate răspunsurile de aici sunt foarte bune; dar numai unul dintre ele (cel mai votat) se referă la modul în care funcționează codul dvs.. Celelalte se referă la generatoare în general și la modul în care funcționează.

Așa că nu voi repeta ce sunt generatoarele sau ce fac randamentele; cred că acestea sunt acoperite de răspunsurile mari existente. Cu toate acestea, după ce am petrecut câteva ore încercând să înțeleg un cod similar cu al tău, voi descompune modul în care funcționează.

Codul tău traversează o structură de arbore binar. Să luăm ca exemplu acest arbore:

    5
   / 
  3   6
 /    
1   4   8

Și o altă implementare mai simplă a traversării unui arbore de căutare binară:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

Codul de execuție se află pe Tree care implementează __iter__ ca acesta:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

while candidates poate fi înlocuită cu for element in tree; Python traduce acest lucru în

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Deoarece Node.__iter__ este un generator, codul din interiorul său este executat la fiecare iterație. Astfel, execuția ar arăta astfel:

  1. elementul rădăcină este primul; se verifică dacă are copii din stânga și for îi iterați (să îl numim it1 deoarece este primul obiect iterator).
  2. it are un copil, astfel încât for este executat. for child in self.left creează un nou iterator de la self.left, , care este el însuși un obiect Node (it2)
  3. Aceeași logică ca și în cazul lui 2, și un nou iterator este creat (it3)
  4. Acum am ajuns la capătul stâng al arborelui. it3 nu mai are copii în stânga, deci continuă și yield self.value
  5. La următorul apel la next(it3) se ridică StopIteration și există, deoarece nu are copii la dreapta (ajunge la sfârșitul funcției fără să cedeze nimic).
  6. it1 și it2 sunt încă active – nu sunt epuizate și apelarea lui next(it2) ar produce valori, nu ar ridica StopIteration
  7. Acum ne-am întors la it2 context, și apelăm next(it2) care continuă de unde s-a oprit: imediat după yield child declarație. Deoarece nu mai are copii rămași, continuă și produce valoarea lui self.val.

Problema aici este că la fiecare iterație creează subiteratori pentru a parcurge arborele și păstrează starea iteratorului curent. Odată ajuns la sfârșit, acesta parcurge înapoi stiva, iar valorile sunt returnate în ordinea corectă (prima valoare este cea mai mică).

Exemplul dvs. de cod a făcut ceva similar printr-o tehnică diferită: a populat un listă cu un singur element pentru fiecare copil, apoi, la următoarea iterație, o scoate și execută codul funcției pe obiectul curent (de aici și self).

Sper că am contribuit puțin la acest subiect legendar. Am petrecut câteva ore bune desenând acest proces pentru a-l înțelege.

Aaron_ab

Poate, de asemenea, să trimită date înapoi la generator!

Într-adevăr, așa cum explică multe răspunsuri de aici, folosind yield creează un generator.

Puteți utiliza yield pentru a trimiteți datele înapoi la un generator „viu”.

Exemplu:

Să spunem că avem o metodă care traduce din engleză în altă limbă. La începutul acesteia, se face ceva care este greu și care trebuie făcut o singură dată. Vrem ca această metodă să ruleze la nesfârșit (nu prea știu de ce.. :)), și să primească cuvinte cuvinte care să fie traduse.

def translator():
    # load all the words in English language and the translation to 'other lang'
    my_words_dict = {'hello': 'hello in other language', 'dog': 'dog in other language'}

    while True:
        word = (yield)
        yield my_words_dict.get(word, 'Unknown word...')

Se execută:

my_words_translator = translator()

next(my_words_translator)
print(my_words_translator.send('dog'))

next(my_words_translator)
print(my_words_translator.send('cat'))

va imprima:

dog in other language
Unknown word...

Pentru a rezuma:

use send în interiorul unui generator pentru a trimite date înapoi la generator. Pentru a permite acest lucru, trebuie să se utilizeze o (yield) este utilizat.

Swati Srivastava

yield în python este într-un fel similar cu declarația return, cu excepția unor diferențe. Dacă mai multe valori trebuie returnate de la o funcție, instrucțiunea return va returna toate valorile sub forma unei liste, care trebuie stocată în memorie în blocul apelantului. Dar ce se întâmplă dacă nu dorim să folosim memorie suplimentară? În schimb, dorim să obținem valoarea din funcție atunci când avem nevoie de ea. Aici intervine randamentul. Luați în considerare următoarea funcție :-

def fun():
   yield 1
   yield 2
   yield 3

Iar apelantul este :-

def caller():
   print ('First value printing')
   print (fun())
   print ('Second value printing')
   print (fun())
   print ('Third value printing')
   print (fun())

Segmentul de cod de mai sus (funcția apelantă), atunci când este apelată, produce rezultate :-

First value printing
1
Second value printing
2
Third value printing
3

După cum se poate observa din cele de mai sus, yield returnează o valoare apelantului său, dar atunci când funcția este apelată din nou, aceasta nu pornește de la prima instrucțiune, ci de la instrucțiunea imediat după yield. În exemplul de mai sus, a fost tipărită „First value printing” și funcția a fost apelată. 1 a fost returnat și tipărit. Apoi a fost imprimată „Second value printing” și a fost apelată din nou fun(). În loc să se tipărească 1 (prima instrucțiune), s-a returnat 2, adică instrucțiunea imediat după yield 1. Același proces se repetă în continuare.

Comentarii

  • Dacă încercați să rulați acest cod, se afișează print(fun()) nu tipărește numere. În schimb, acesta tipărește reprezentarea obiectului generator returnat de către fun() (ceva de genul <generator object fun at 0x6fffffe795c8>) –  > Por Tocilari amuzanți.
  • @FunnyGeeks Am rulat același cod pe Jupyter Notebook și funcționează bine. De asemenea, scopul aici a fost să explicăm funcționarea cuvântului cheie yield. Fragmentul este doar în scop demonstrativ. –  > Por Swati Srivastava.
  • Am încercat în python2 și python3 în consola mea cygwin. Nu a funcționat. github.com/ImAmARobot/PythonTest –  > Por Funny Geeks.