38 Pytanie: Co robi słowo kluczowe „wydaj”?

pytanie utworzone w Thu, May 2, 2019 12:00 AM

Jakie jest użycie słowa kluczowego yield w Pythonie? Co to robi?

Na przykład próbuję zrozumieć ten kod 1 :

 
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 to jest dzwoniący:

 
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

Co się dzieje, gdy wywoływana jest metoda _get_child_candidates? Czy lista została zwrócona? Jeden element? Czy to się nazywa ponownie? Kiedy kolejne połączenia zostaną przerwane?


1. Ten fragment kodu został napisany przez Jochena Schulza (jrschulz), który stworzył świetną bibliotekę Pythona dla przestrzeni metrycznych. To jest link do kompletnego źródła: mspace modułu .

    
9314
30 odpowiedzi                              30                         

Aby zrozumieć, co robi yield, musisz zrozumieć, czym są generatory . Zanim zrozumiesz generatory, musisz zrozumieć iterables .

Iterables

Podczas tworzenia listy możesz czytać jej elementy jeden po drugim. Odczytywanie jego pozycji po kolei nazywa się iteracją:

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

mylist to iterowalny . Kiedy używasz rozumienia listy, tworzysz listę, a więc iterowalny:

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

Wszystko, czego możesz użyć „for... in...”, jest iterowalne; lists, strings, pliki ...

Te iterable są przydatne, ponieważ możesz je czytać tyle, ile chcesz, ale przechowujesz wszystkie wartości w pamięci i nie zawsze jest to, czego chcesz, gdy masz dużo wartości.

Generatory

Generatory są iteratorami, rodzajem iterowalnego , który można iterować tylko raz . Generatory nie przechowują wszystkich wartości w pamięci, generują wartości w locie :

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

Jest taki sam, z wyjątkiem tego, że użyłeś () zamiast []. ALE, nie możesz wykonać for i in mygenerator po raz drugi, ponieważ generatory mogą być użyte tylko raz: obliczają 0, potem o tym zapominają i obliczają 1, a kończą obliczanie 4, jeden po drugim.

Wydajność

yield to słowo kluczowe, które jest używane jak return, z wyjątkiem tego, że funkcja zwróci generator.

 
>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Tutaj jest bezużyteczny przykład, ale jest przydatny, gdy wiesz, że twoja funkcja zwróci ogromny zestaw wartości, które będziesz musiał przeczytać tylko raz.

Aby opanować yield, musisz zrozumieć, że gdy wywołujesz funkcję, kod, który napisałeś w treści funkcji, nie jest uruchamiany. Funkcja zwraca tylko obiekt generatora, jest to trochę skomplikowane :-)

Następnie kod będzie kontynuowany od miejsca, w którym został przerwany za każdym razem, gdy for używa generatora.

Teraz trudna część:

Za pierwszym razem, gdy for wywoła obiekt generatora utworzony z twojej funkcji, uruchomi kod w twojej funkcji od początku, aż osiągnie yield, a następnie zwróci pierwszą wartość pętli. Następnie każde inne wywołanie uruchomi pętlę, którą zapisałeś w funkcji jeszcze raz, i zwróci następną wartość, aż nie będzie wartości do zwrócenia.

Generator jest uważany za pusty po uruchomieniu funkcji, ale nie trafia już w yield. Może to wynikać z tego, że pętla dobiegła końca, lub dlatego, że nie zaspokoiłeś już "if/else".


Twój kod został wyjaśniony

Generator:

 
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Dzwoniący:

 
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Ten kod zawiera kilka inteligentnych części:

  • Pętla iteruje na liście, ale lista rozszerza się podczas iteracji pętli :-) Jest to zwięzły sposób, aby przejść przez wszystkie zagnieżdżone dane, nawet jeśli jest to trochę niebezpieczne, ponieważ można skończyć z nieskończona pętla. W tym przypadku candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) wyczerpuje wszystkie wartości generatora, ale while tworzy nowe obiekty generatora, które będą wytwarzać różne wartości z poprzednich, ponieważ nie są stosowane w tym samym węźle.

  • Metoda extend() jest metodą obiektu listy, która oczekuje iteracji i dodaje jej wartości do listy.

Zazwyczaj przekazujemy listę:

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

Ale w twoim kodzie dostaje generator, co jest dobre, ponieważ:

  1. Nie musisz dwukrotnie czytać wartości.
  2. Możesz mieć dużo dzieci i nie chcesz, aby wszystkie były przechowywane w pamięci.

I działa, ponieważ Python nie obchodzi, czy argument metody jest listą, czy nie. Python oczekuje iterowalności, więc będzie działać z ciągami, listami, krotkami i generatorami! Nazywa się to pisaniem na kaczkę i jest jednym z powodów, dla których Python jest tak fajny. Ale to kolejna historia, na inne pytanie ...

Możesz zatrzymać się tutaj lub przeczytać trochę, aby zobaczyć postępd użycie generatora:

Sterowanie wyczerpaniem generatora

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

Uwaga: w Pythonie 3 użyj print(corner_street_atm.__next__()) lub print(next(corner_street_atm))

Może być przydatny do różnych rzeczy, takich jak kontrolowanie dostępu do zasobu.

Itertools, twój najlepszy przyjaciel

Moduł itertools zawiera specjalne funkcje do manipulowania iteracjami. Czy kiedykolwiek chciałbyś powielić generator? Łańcuch dwóch generatorów? Grupuj wartości w zagnieżdżonej liście za pomocą jednego linera? Map / Zip bez tworzenia innej listy?

Wtedy tylko import itertools.

Przykład? Zobaczmy możliwe rozkazy przybycia na wyścig czterech koni:

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

Zrozumienie wewnętrznych mechanizmów iteracji

Iteracja to proces implikujący iterable (implementacja metody __iter__()) i iteratory (implementacja metody __next__()). Iterables to dowolne obiekty, z których można uzyskać iterator. Iteratory to obiekty, które umożliwiają iterację iterable.

Więcej na ten temat w tym artykule o jak działa for pętli .

    
13495
2019-05-02 19: 34: 54Z
  1. Wszystkie iteratory mogą być iterowane tylko raz, a nie tylko te generowane przez funkcje generatora. Jeśli mi nie wierzysz, zadzwoń pod iter() na dowolny obiekt iterowalny i spróbuj powtórzyć wynik więcej niż raz.
    2015-03-03 22: 54: 31Z
  2. @ Craicerjack Masz pomieszane terminy. Iterowalna jest metoda __iter__. Iterator jest wynikiem wywołania iter() w iterowanej. Iteratory można powtarzać tylko raz.
    2015-08-24 20: 52: 18Z
  3. yield nie jest tak magiczna, jak sugeruje ta odpowiedź. Gdy wywołujesz funkcję zawierającą instrukcję yield w dowolnym miejscu, otrzymujesz obiekt generatora, ale nie działa żaden kod. Następnie za każdym razem, gdy wyodrębniasz obiekt z generatora, Python wykonuje kod w funkcji, dopóki nie dojdzie do instrukcji yield, a następnie wstrzymuje i dostarcza obiekt. Kiedy wyodrębniasz inny obiekt, Python wznawia działanie tuż po yield i kontynuuje, aż osiągnie kolejny yield (często ten sam, ale później jedną iterację). Jest to kontynuowane, dopóki funkcja nie zakończy działania, w którym to momencie generator zostanie uznany za wyczerpany.
    2017-05-23 21: 41: 53Z
  4. "Te iterable są przydatne ... ale przechowujesz wszystkie wartości w pamięci i to nie zawsze jest to, czego chcesz", jest albo błędne, albo mylące. Iterable zwraca iterator po wywołaniu iter () w iterowalnym, a iterator nie zawsze musi przechowywać swoje wartości w pamięci, w zależności od implementacji metody iter , może również generować wartości w kolejności na żądanie.
    2018-02-15 19: 21: 11Z

Skrót do zrozumienia yield

Gdy zobaczysz funkcję zawierającą yield instrukcji, zastosuj tę prostą sztuczkę, aby zrozumieć, co się stanie:

  1. Wstaw linię result = [] na początku funkcji.
  2. Zastąp każde yield expr result.append(expr).
  3. Wstaw linię return result na dole funkcji.
  4. Yay - nie więcej yield instrukcji! Przeczytaj i wymyśl kod.
  5. Porównaj funkcję z pierwotną definicją.

Ta sztuczka może dać ci wyobrażenie o logice stojącej za tą funkcją, ale to, co dzieje się z yield, różni się znacznie od tego, co dzieje się w podejściu opartym na liście. W wielu przypadkach podejście do wydajności będzie dużo wydajniejsze i szybsze. W innych przypadkach ta sztuczka sprawi, że utkniesz w nieskończonej pętli, nawet jeśli oryginalna funkcja działa dobrze. Czytaj dalej, aby dowiedzieć się więcej ...

Nie myl swoich Iterables, Iteratorów i generatorów

Najpierw protokół iteratora - kiedy piszesz

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

Python wykonuje następujące dwa kroki:

  1. Pobiera iterator dla mylist:

    Zadzwoń iter(mylist) - > zwraca obiekt z metodą next() (lub __next__() w Pythonie 3).

    [Jest to krok, o którym większość ludzi zapomina powiedzieć]

  2. Używa iteratora do przewijania elementów:

    Kontynuuj wywoływanie metody next() na zwróconym iteratorze from krok 1. Wartość zwracana z next() jest przypisana do x, a ciało pętli jest wykonywane. Jeśli wyjątek StopIteration zostanie podniesiony z poziomu next(), oznacza to, że nie ma więcej wartości w iteratorze i pętla zostanie zamknięta.

Prawda jest taka, że ​​Python wykonuje powyższe dwa kroki za każdym razem, gdy chce zapętlić zawartość obiektu - może to być pętla for, ale może to być również kod taki jak otherlist.extend(mylist) (gdzie otherlist to lista Pythona).

Tutaj mylist jest iterowalnym , ponieważ implementuje protokół iteratora. W klasie zdefiniowanej przez użytkownika można zaimplementować metodę __iter__(), aby instancje klasy były iterowalne. Ta metoda powinna zwrócić iterator . Iterator jest obiektem z metodą next(). Możliwe jest zaimplementowanie obu __iter__() i next() w tej samej klasie i __iter__() zwrot self. Będzie to działać w prostych przypadkach, ale nie wtedy, gdy chcesz, aby dwa iteratory zapętlały ten sam obiekt w tym samym czasie.

Więc to jest protokół iteratora, wiele obiektów implementuje ten protokół:

  1. Wbudowane listy, słowniki, krotki, zestawy, pliki.
  2. Klasy zdefiniowane przez użytkownika, które implementują __iter__().
  3. Generatory.

Zauważ, że pętla for nie wie, z jakim rodzajem obiektu ma do czynienia - po prostu podąża za protokołem iteratora i cieszy się, że otrzymuje element po elemencie wywołując next(). Wbudowane listy zwracają swoje elementy jeden po drugim, słowniki zwracają klucze jeden po drugim, pliki zwracają wiersze jeden po drugim itd. A generatory wracają ... dobrze tam wchodzi yield:

 
def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Zamiast yield instrukcji, jeśli masz return instrukcji w f123(), tylko pierwsze zostanie wykonane, a funkcja zostanie zamknięta. Ale f123() nie jest zwykłą funkcją. Po wywołaniu f123() nie zwraca żadnej wartości w deklaracjach wydajności! Zwraca obiekt generatora. Ponadto funkcja tak naprawdę nie wychodzi - przechodzi w stan zawieszenia. Gdy pętla for próbuje zapętlić obiekt generatora, funkcja wznawia działanie od stanu zawieszenia w następnej linii po yield, z którego wcześniej zwrócił, wykonuje następną linię kodu, w tym przypadku instrukcję yield i zwraca to jako następny element. Dzieje się tak, dopóki funkcja nie zakończy pracy, w którym to momencie generator podnosi StopIteration, a pętla wychodzi.

Tak więc obiekt generatora przypomina coś w rodzaju adaptera - na jednym końcu pokazuje protokół iteratora, ujawniając metody __iter__() i next(), aby pętla for była szczęśliwa. Na drugim końcu jednak uruchamia funkcję na tyle, aby uzyskać z niej następną wartość i przywraca ją do trybu zawieszenia.

Dlaczego warto korzystać z generatorów?

Zazwyczaj można pisać kod, który nie używa generatorów, ale implementuje tę samą logikę. Jedną z opcji jest użycie sztuczki listy tymczasowej, o której wspomniałem wcześniej. To nie zadziała we wszystkich przypadkach, np. jeśli masz nieskończone pętle lub może to spowodować nieefektywne wykorzystanie pamięci, gdy masz naprawdę długą listę. Innym podejściem jest zaimplementowanie nowej iterowalnej klasy SomethingIter, która utrzymuje stan w elementach instancji i wykonuje kolejny logiczny krok w metodzie next() (lub __next__() w Pythonie 3). W zależności od logiki kod wewnątrz metody next() może wyglądać na bardzo skomplikowany i podatny na błędy. Tutaj generatory zapewniają czyste i łatwe rozwiązanie.

    
1810
2019-04-24 09: 37: 06Z
  1. "Kiedy zobaczysz funkcję z deklaracjami wydajności, zastosuj tę prostą sztuczkę, aby zrozumieć, co się stanie" Czy to nie ignoruje tego faktu że możesz send w generator, który jest ogromną częścią punktu generatorów?
    2017-06-17 22: 41: 34Z
  2. "może to być pętla for, ale może to być również kod taki jak otherlist.extend(mylist)" - > To jest niepoprawne. extend() modyfikuje listę w miejscu i nie zwraca iterowanej. Próba zapętlenia ponad otherlist.extend(mylist) nie powiedzie się z TypeError, ponieważ extend() niejawnie zwraca None, a ty nie możesz zapętlić None.
    2017-09-14 14: 48: 17Z
  3. @ pedro Źle zrozumiałeś to zdanie. Oznacza to, że python wykonuje dwa wymienione kroki na mylist (nie na otherlist) podczas wykonywania otherlist.extend(mylist).
    2017-12-26 18: 53: 57Z

Pomyśl o tym w ten sposób:

Iterator jest po prostu wymyślnym określeniem dla obiektu, który ma metodę next(). Tak więc funkcja o zwiększonej wydajności kończy się tak:

Oryginalna wersja:

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

for i in some_function():
    print i

Jest to w zasadzie to, co interpreter Pythona robi z powyższym kodem:

 
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

Aby uzyskać więcej informacji na temat tego, co dzieje się za kulisami, pętlę for można przepisać do tego:

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

Czy to ma więcej sensu lub po prostu bardziej cię myli? :)

Powinienem zauważyć, że to jest nadmiernym uproszczeniem dla celów ilustracyjnych. :)

    
476
2019-05-07 13: 28: 35Z
  1. __getitem__ można zdefiniować zamiast __iter__. Na przykład: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), wydrukuje: 0, 10, 20, ..., 90
    2008-10-25 02: 03: 38Z
  2. Próbowałem tego przykładu w Pythonie 3.6 i jeśli utworzę iterator = some_function(), zmienna iterator nie ma już funkcji o nazwie next(), ale tylko funkcję __next__(). Pomyślałem, że o tym wspomnę.
    2017-05-06 14: 37: 55Z

Słowo kluczowe yield zostało zredukowane do dwóch prostych faktów:

  1. Jeśli kompilator wykryje słowo kluczowe yield w dowolnym miejscu wewnątrz funkcji, funkcja ta nie zwraca się już za pomocą instrukcji return. Inste , natychmiast zwróci leniwy obiekt „oczekującej listy” zwany generatorem
  2. Generator jest iterowalny. Co to jest iterowalny ? Jest to coś takiego jak list lub set lub range lub widok dyktowania, z wbudowanym protokołem do odwiedzania każdego elementu w określonej kolejności .

W skrócie: generator jest leniwą listą oczekującą na przyrost , a yield instrukcji pozwala na użycie notacji funkcji do programowania wartości listy , generator powinien przyrostowo wypluć.

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

Przykład

Zdefiniujmy funkcję makeRange, która jest taka sama jak range Pythona. Dzwonienie makeRange(n) ZWRACA GENERATORA:

 
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>

Aby zmusić generator do natychmiastowego zwracania oczekujących wartości, możesz przekazać go do list() (tak jak każdy inny iterowalny):

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

Porównywanie przykładu z „właśnie zwracaniem listy”

Powyższy przykład można traktować jedynie jako tworzenie listy, do której dodajesz i wracasz:

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

Istnieje jednak jedna zasadnicza różnica; zobacz ostatnią sekcję.


Jak używać generatorów

Iterowalność jest ostatnią częścią rozumienia listy, a wszystkie generatory są iterowalne, więc często są używane w następujący sposób:

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

Aby lepiej poczuć generatory, możesz bawić się z modułem itertools (pamiętaj, aby użyć chain.from_iterable zamiast chain, gdy jest to uzasadnione). Na przykład możesz użyć generatorów do implementacji nieskończenie długich list leniwych, takich jak itertools.count(). Możesz zaimplementować własne def enumerate(iterable): zip(count(), iterable) lub alternatywnie za pomocą słowa kluczowego yield w pętli while.

Uwaga: generatory mogą być w rzeczywistości używane do wielu innych rzeczy, takich jak wdrażanie coroutines lub programowanie niedeterministyczne lub inne eleganckie rzeczy. Jednak punkt widzenia „leniwe listy”, który tutaj prezentuję, jest najczęściej używanym narzędziem.


Za kulisami

Tak działa „protokół iteracji Pythona”. To znaczy, co się dzieje, kiedy robisz list(makeRange(5)). To jest to, co opisuję wcześniej jako „leniwa, przyrostowa lista”.

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

Wbudowana funkcja next() po prostu wywołuje funkcję .next(), która jest częścią „protokołu iteracji” i znajduje się na wszystkich iteratorach. Możesz ręcznie użyć funkcji next() (i innych części protokołu iteracji), aby zaimplementować fantazyjne rzeczy, zwykle kosztem czytelności, więc staraj się tego nie robić ...


Minutiae

Zazwyczaj większość ludzi nie przejmuje się następującymi różnicami i prawdopodobnie chce przestać czytać tutaj.

W mowie Pythona iterowalny jest dowolnym obiektem, który „rozumie pojęcie pętli for-for”, takim jak lista [1,2,3], a iterator jest konkretną instancją żądanej pętli for-like takiej jak [1,2,3].__iter__(). Generator jest dokładnie taki sam jak każdy iterator, z wyjątkiem sposobu, w jaki został napisany (ze składnią funkcji).

Gdy żądasz iteratora z listy, tworzy on nowy iterator. Jednak kiedyzrównaj iterator z iteratorem (co rzadko bywało), po prostu daje ci kopię samego siebie.

Tak więc, w mało prawdopodobnym przypadku, gdy nie robisz czegoś takiego ...

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

... pamiętaj, że generator to iterator ; to znaczy jednorazowe użycie. Jeśli chcesz go ponownie użyć, ponownie zadzwoń pod numer myRange(...). Jeśli chcesz użyć wyniku dwa razy, przekonwertuj wynik na listę i zapisz go w zmiennej x = list(myRange(5)). Ci, którzy absolutnie potrzebują sklonować generator (na przykład, którzy robią przerażająco hackowskie metaprogramowanie), mogą użyć itertools.tee , jeśli jest to absolutnie konieczne, ponieważ kopiowany iterator Python PEP propozycja standardów została odroczona.

    
402
2017-03-19 08: 07: 35Z
  

Co robi słowo kluczowe yield w Pythonie?

Zarys odpowiedzi /Podsumowanie

  • Funkcja z yield , gdy zostanie wywołana , zwraca Generator .
  • Generatory są iteratorami, ponieważ implementują protokół iteratora , aby można było je przeglądać.
  • Generator może być także wysłaną informacją , co sprawia, że ​​jest koncepcyjnie korektą .
  • W Pythonie 3 możesz delegować z jednego generatora do drugiego w obu kierunkach z yield from .
  • (dodatek krytykuje kilka odpowiedzi, w tym pierwszą, i omawia użycie return w generatorze.)

Generatory:

yield to tylko legalna definicja definicji funkcji, a włączenie yield do definicji funkcji powoduje zwrócenie generatora.

Pomysł na generatory pochodzi z innych języków (patrz przypis 1) z różnymi implementacjami. W Generatorach Pythona wykonanie kodu jest zamrożone w punkt plonu. Po wywołaniu generatora (metody są omówione poniżej) wykonanie zostaje wznowione, a następnie zawiesza się przy następnej wydajności.

yield zapewnia łatwy sposób implementacji protokołu iteratora , zdefiniowanego przez dwa kolejne metody:  __iter__ i next (Python 2) lub __next__ (Python 3). Obie te metody uczynić obiekt iteratorem, który można sprawdzić za pomocą bazy abstrakcyjnej Iterator Klasa z modułu 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.

Typ generatora to podtyp iteratora:

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

I jeśli to konieczne, możemy wpisać tekst w ten sposób:

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

Funkcja Iterator polega na tym, że po wyczerpaniu możesz nie używaj go ponownie ani nie resetuj:

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

Będziesz musiał zrobić kolejny, jeśli chcesz ponownie użyć jego funkcjonalności (patrz przypis 2):

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

Dane można uzyskać programowo, na przykład:

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

Powyższy prosty generator jest także równoważny poniższemu - od wersji Python 3.3 (i nie jest dostępny w Pythonie 2), możesz użyć yield from :

 
def func(an_iterable):
    yield from an_iterable

Jednak yield from pozwala także na delegowanie do generatorów, co zostanie wyjaśnione w następnym rozdziale dotyczącym delegowania współpracy z podrzędnymi.

Coroutines:

yield tworzy wyrażenie, które umożliwia wysyłanie danych do generatora (patrz przypis 3)

Oto przykład, zwróć uwagę na zmienną received, która wskaże dane wysyłane do generatora:

 
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)

Najpierw musimy ustawić generator w kolejce za pomocą wbudowanej funkcji, next . To będzie wywołać odpowiednią metodę next lub __next__, w zależności od wersji Python, którego używasz:

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

A teraz możemy wysyłać dane do generatora. ( Wysyłanie None to tak samo jak dzwonienie next .):

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

Delegacja spółdzielcza doSub-Coroutine z yield from

Przypomnij sobie, że yield from jest dostępny w Pythonie 3. Pozwala nam to na delegowanie Linie do podkategorii:

 
def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

A teraz możemy delegować funkcjonalność do generatora podrzędnego i można go użyć przez generator tak jak powyżej:

 
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Możesz przeczytać więcej na temat precyzyjnej semantyki yield from w PEP 380 .

Inne metody: zamknij i rzuć

Metoda close podnosi GeneratorExit w punkcie funkcji wykonanie zostało zamrożone. Będzie to również wywoływane przez __del__, więc ty możesz umieścić dowolny kod czyszczenia w miejscu obsługi GeneratorExit:

 
>>> my_account.close()

Możesz także rzucić wyjątek, który może być obsługiwany w generatorze lub propagowane z powrotem do użytkownika:

 
>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Wniosek

Uważam, że omówiłem wszystkie aspekty poniższego pytania:

  

Co robi słowo kluczowe yield w Pythonie?

Okazuje się, że yield robi dużo. Jestem pewien, że mógłbym dodać jeszcze więcej dokładne przykłady tego. Jeśli chcesz więcej lub masz konstruktywną krytykę, daj mi znać, komentując poniżej.


Dodatek:

Krytyka najwyższej /zaakceptowanej odpowiedzi **

  • Jest niejasne, co sprawia, że ​​ iterowalny , używając tylko przykładu jako przykładu. Zobacz moje referencje powyżej, ale w skrócie: iterowalny ma metodę __iter__ zwracającą iterator . iterator zapewnia metodę .next (Python 2 lub .__next__ (Python 3)), która jest niejawnie wywoływana przez for pętli, dopóki nie podniesie się do StopIteration, a gdy to zrobi, będzie nadal to robić.
  • Następnie używa wyrażenia generatora, aby opisać, czym jest generator. Ponieważ generator jest po prostu wygodnym sposobem utworzenia iteratora , to tylko myli sprawę, a my jeszcze nie dotarliśmy do części yield.
  • W Kontrolowaniu wyczerpania generatora wywołuje metodę .next, gdy zamiast tego powinien użyć wbudowanej funkcji, next. Byłaby to odpowiednia warstwa pośrednia, ponieważ jego kod nie działa w Pythonie 3.
  • Itertools? Nie dotyczyło to tego, co yield robi w ogóle.
  • Brak dyskusji na temat metod, które yield zapewnia wraz z nową funkcjonalnością yield from w Pythonie 3. Najlepsza /zaakceptowana odpowiedź to bardzo niepełna odpowiedź.

Krytyka odpowiedzi sugerująca yield w wyrażeniu generatora lub rozumieniu.

Gramatyka obecnie pozwala na dowolne wyrażenie w rozumieniu listy.

 
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

Ponieważ wydajność jest wyrażeniem, niektórzy twierdzą, że jest ona interesująca w używaniu jej w wyrażeniach lub wyrażeniach generatora - pomimo tego, że nie podają szczególnie dobrego przypadku użycia.

Główni programiści CPythona to omawianie potępiania jego dodatku . Oto odpowiedni post z listy mailingowej:

  

30 stycznia 2017 o 19:05 Brett Cannon napisał:

     
    

W niedzielę, 29 stycznia 2017 o 16:39 Craig Rodrigues napisał:

         
      

Jestem w porządku z każdym podejściem. Pozostawienie rzeczy tak, jak są w Pythonie 3       nie jest dobre, IMHO.

    
         

Mój głos to błąd SyntaxError, ponieważ nie otrzymujesz tego, czego oczekujesz     składnia.

  
     

Zgadzam się, że to rozsądne miejsce, w którym możemy skończyć, jak każdy kod   poleganie na obecnym zachowaniu jest naprawdę zbyt sprytne, by być   możliwy do utrzymania.

     

Jeśli chodzi o dotarcie na miejsce, prawdopodobnie będziemy chcieli:

     
  • SyntaxWarning lub DeprecationWarning w 3.7
  •   
  • Ostrzeżenie Py3k w wersji 2.7.x
  •   
  • Błąd składni w 3.8
  •   

Pozdrawiam, Nick.

     

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

Ponadto istnieje nierozstrzygnięty problem (10544) , który wydaje się wskazywać ten kierunek nigdy nie jest dobrym pomysłem (PyPy, implementacja Pythona napisana w Pythonie, już podnosi ostrzeżenia składniowe.)

Dolna linia, dopóki programiści CPython nie powiedzą nam inaczej: Nie umieszczaj yield w wyrażeniu generatora lub zrozumieniu.

Instrukcja return w generatorze

W Pythonie 2 :

  

W funkcji generatora instrukcja return nie może zawierać expression_list. W tym kontekście nagi return wskazuje, że generator został wykonany i spowoduje podniesienie StopIteration.

expression_list to w zasadzie dowolna liczba wyrażeń oddzielonych przecinkami - zasadniczo w Pythonie 2 możesz zatrzymać generator za pomocą return, ale nie możeszzwróć wartość.

W Pythonie 3 :

  

W funkcji generatora instrukcja return wskazuje, że generator został wykonany i spowoduje podniesienie StopIteration. Zwracana wartość (jeśli istnieje) jest używana jako argument do skonstruowania StopIteration i staje się atrybutem StopIteration.value.

Przypisy

  1. Języki CLU, Sather i Icon zostały wymienione w propozycji wprowadzenie koncepcji generatorów do Pythona. Ogólna idea jest że funkcja może utrzymywać wewnętrzny stan i dawać pośredni punkty danych na żądanie użytkownika. Zapowiadało się to lepsze wyniki do innych podejść, w tym wątków Pythona , które nie są nawet dostępne w niektórych systemach.

  2. Oznacza to na przykład, że xrange obiektów (range w Pythonie 3) nie ma Iterator s, nawet jeśli są iterowalne, ponieważ mogą być ponownie użyte. Podobnie jak listy, ich metody __iter__ zwracają obiekty iteracyjne.

  3. yield został pierwotnie wprowadzony jako instrukcja, co oznacza, że może pojawić się tylko na początku linii w bloku kodu. Teraz yield tworzy wyrażenie wydajności. https://docs.python.org/2/reference/simple_stmts .html # grammar-token-yield_stmt Ta zmiana była proponowana , aby umożliwić użytkownik do wysyłania danych do generatora ktoś mógłby to otrzymać. Aby wysłać dane, trzeba mieć możliwość przypisania ich do czegoś i dlatego instrukcja po prostu nie będzie działać.

321
2017-12-06 20: 20: 40Z

yield jest dokładnie jak return - zwraca wszystko, co mu powiesz (jako generator). Różnica polega na tym, że przy następnym wywołaniu generatora wykonanie zaczyna się od ostatniego wywołania do instrukcji yield. W przeciwieństwie do powrotu, ramka stosu nie jest czyszczona po wystąpieniu wydajności, jednak kontrola jest przenoszona z powrotem do dzwoniącego, więc jego stan zostanie wznowiony przy następnym wywołaniu funkcji.

W przypadku twojego kodu, funkcja get_child_candidates działa jak iterator, więc po rozszerzeniu listy dodaje jeden element naraz do nowej listy.

list.extend wywołuje iterator, dopóki nie zostanie wyczerpany. W przypadku próbki kodu, którą zamieściłeś, znacznie wyraźniej byłoby po prostu zwrócić krotkę i dodać ją do listy.

    
276
2019-01-24 09: 39: 59Z
  1. To jest blisko, ale nie jest poprawne. Za każdym razem, gdy wywołujesz funkcję z instrukcją yield, zwraca ona nowy obiekt generatora. Dopiero po wywołaniu tej metody .next () generatora wykonanie zostanie wznowione po ostatniej wydajności.
    2008-10-24 18: 11: 04Z

Jest jeszcze jedna dodatkowa rzecz: funkcja, której wydajność nie musi się kończyć. Napisałem taki kod:

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

Wtedy mogę użyć go w innym kodzie, takim jak ten:

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

To naprawdę pomaga uprościć niektóre problemy i ułatwia pracę z niektórymi rzeczami.

    
209
2013-04-21 15: 42: 14Z

Dla tych, którzy preferują minimalny przykład pracy, medytuj nad tą interaktywną sesją Pythona:

 
>>> 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
    
184
2019-05-08 12: 20: 10Z

TL;DR

Zamiast tego:

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

zrób to:

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

Zawsze, gdy znajdziesz yosamodzielnie buduj listę od zera, yield każdy kawałek zamiast tego.

To był mój pierwszy moment „aha” z wydajnością.


yield to słodki sposób mówienia

  

zbuduj serię rzeczy

Takie samo zachowanie:

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

Różne zachowanie:

Wydajność to jednoprzebiegowy : możesz przejść tylko raz. Kiedy funkcja ma w sobie wydajność, nazywamy ją funkcją generatora . I iterator jest tym, co zwraca. Te warunki są ujawniające. Tracimy wygodę kontenera, ale zyskujemy moc serii obliczanej w razie potrzeby i arbitralnie długiej.

Wydajność jest leniwa , odkłada obliczenia. Funkcja z wydajnością w rzeczywistości nie jest wykonywana, kiedy ją wywołujesz. Zwraca obiekt iteratora , który pamięta, gdzie został przerwany. Za każdym razem, gdy wywołujesz next() na iteratorze (dzieje się to w pętli for), przeskakujesz o cale do następnej wydajności. return podnosi StopIteration i kończy serię (jest to naturalny koniec pętli for).

Wydajność to wszechstronny . Dane nie muszą być przechowywane razem, można je udostępniać pojedynczo. To może być nieskończone.

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

Jeśli potrzebujesz wielu podań , a seria nie jest zbyt długa, po prostu zadzwoń pod numer list():

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

Świetny wybór słowa yield, ponieważ obowiązują oba znaczenia :

  

wydajność - produkuj lub dostarczaj (jak w rolnictwie)

... podaj kolejne dane z serii.

  

wydajność - ustąp lub zrezygnuj (jak w sile politycznej)

... zrezygnuj z wykonywania procesora, dopóki iterator nie przyspieszy.

    
174
2019-01-04 15: 30: 21Z

Wydajność daje 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

Jak widać, w pierwszym przypadku foo przechowuje jednocześnie całą listę w pamięci. To nie jest wielka sprawa dla listy z 5 elementami, ale co, jeśli chcesz listę 5 milionów? Jest to nie tylko ogromny eater pamięci, ale także wymaga dużo czasu na zbudowanie tej funkcji.

W drugim przypadku bar daje tylko generator. Generator jest iterowalny - co oznacza, że ​​można go używać w pętli for itd., Ale do każdej wartości można uzyskać dostęp tylko raz. Wszystkie wartości nie są jednocześnie przechowywane w pamięci; obiekt generatora „pamięta”, gdzie był w pętli, kiedy ostatni raz go nazwałeś - w ten sposób, jeśli używasz powtarzalnego (powiedzmy) do 50 miliardów, nie musisz liczyć do 50 miliardów wszystkich od razu i zapisz 50 miliardów liczb do policzenia.

Ponownie, jest to całkiem wymyślny przykład, prawdopodobnie użyjesz itertools, jeśli naprawdę chcesz policzyć do 50 miliardów. :)

To najprostszy przypadek użycia generatorów. Jak powiedziałeś, można go używać do pisania wydajnych permutacji, używając wydajności do wypychania rzeczy przez stos wywołań zamiast używania jakiejś zmiennej stosu. Generatory mogą być również używane do wyspecjalizowanego przemierzania drzew i wszelkich innych rzeczy.

    
160
2019-03-13 06: 04: 08Z
  1. Tylko uwaga - w Pythonie 3, range zwraca również generator zamiast listy, więc zobaczysz również podobny pomysł, z wyjątkiem tego, że __repr__/__str__ są zastępowane aby pokazać lepszy wynik, w tym przypadku range(1, 10, 2).
    2019-03-21 18: 33: 13Z

Zwraca generator. Nie jestem szczególnie zaznajomiony z Pythonem, ale wierzę, że jest to ten sam rodzaj rzeczy, co bloki iteracyjne C # jeśli znasz te elementy.

Kluczową ideą jest to, że kompilator /interpreter /cokolwiek robi jakieś oszustwa, tak że jeśli chodzi o rozmówcę, mogą nadal wywoływać next () i zachowa returning values ​​- tak, jakby metoda generatora została wstrzymana . Teraz oczywiście nie możesz tak naprawdę „wstrzymać” metody, więc kompilator buduje automat stanów, abyś pamiętał, gdzie aktualnie jesteś i jak wyglądają lokalne zmienne itp. Jest to znacznie łatwiejsze niż samodzielne pisanie iteratora.

    
153
2018-10-31 08: 42: 59Z

Jest jeden rodzaj odpowiedzi, którego nie czuję, że został podany, wśród wielu wspaniałych odpowiedzi, które opisują, jak używać generatorów. Oto odpowiedź na teorię języka programowania:

Instrukcja yield w Pythonie zwraca generator. Generator w Pythonie to funkcja, która zwraca kontynuacje (a konkretnie rodzaj korekcji, ale kontynuacje reprezentują bardziej ogólny mechanizm pozwalający zrozumieć, co się dzieje).

Kontynuacje w teorii języków programowania są znacznie bardziej podstawowym rodzajem obliczeń, ale nie są często używane, ponieważ są one bardzo trudne do zrozumienia, a także bardzo trudne do wdrożenia. Ale idea tego, czym jest kontynuacja, jest prosta: jest to stan obliczeń, który jeszcze się nie zakończył. W tym stanie aktualne wartości zmiennych, operacje, które jeszcze nie zostały wykonane, i tak dalej, są zapisywane. Następnie w pewnym momencie później można wywołać kontynuację, tak że zmienne programu zostaną zresetowane do tego stanu i zapisane operacje zostaną wykonane.

Kontynuacje, w tej bardziej ogólnej formie, mogą być realizowane na dwa sposoby. W sposobie call/cc stos programu jest dosłownie zapisywany, a następnie po wywołaniu kontynuacji stos jest przywracany.

W stylu przekazywania kontynuacji (CPS) kontynuacje są tylko normalnymi funkcjami (tylko w językach, w których funkcje są pierwszej klasy), które programista wyraźnie zarządza i przekazuje do podprogramów. W tym stylu stan programu jest reprezentowany przez zamknięcia (i zmienne, które są w nich zakodowane) zamiast zmiennych, które znajdują się gdzieś na stosie. Funkcje zarządzające przepływem sterowania akceptują kontynuację jako argumenty (w niektórych odmianach CPS funkcje mogą akceptować wiele kontynuacji) i manipulować przepływem sterowania, wywołując je po prostu wywołując je i zwracając później. Bardzo prosty przykład stylu przekazywania kontynuacji jest następujący:

 
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)

W tym (bardzo uproszczonym) przykładzie programista zapisuje operację zapisu pliku do kontynuacji (która może być bardzo złożoną operacją z wieloma szczegółami do wypisania), a następnie przekazuje tę kontynuację (tj. zamknięcie pierwszej klasy) do innego operatora, który wykonuje więcej przetwarzania, a następnie w razie potrzeby wywołuje go. (Bardzo często wykorzystuję ten wzorzec projektowy w rzeczywistym programowaniu GUI, ponieważ zapisuje mi linie kodu lub, co ważniejsze, zarządza przepływem sterowania po wyzwalaniu zdarzeń GUI.)

Reszta tego postu, bez utraty ogólności, konceptualizuje kontynuacje jako CPS, ponieważ jest to o wiele łatwiejsze do zrozumienia i przeczytania.


Porozmawiajmy teraz o generatorach w Pythonie. Generatory są specyficznym podtypem kontynuacji. Podczas gdy kontynuacje są na ogół w stanie zapisać stan obliczenia (tzn. Stos wywołań programu), generatory są w stanie zapisać stan iteracji tylko iterator . Chociaż definicja ta jest nieco myląca dla pewnych przypadków użycia generatorów. Na przykład:

 
def f():
  while True:
    yield 4

Jest to wyraźnie rozsądna iterowalność, której zachowanie jest dobrze zdefiniowane - za każdym razem, gdy generator iteruje nad nim, zwraca 4 (i robi to na zawsze). Ale nie jest to prawdopodobnie prototypowy typ iterowalny, który przychodzi na myśl podczas myślenia o iteratorach (tj. for x in collection: do_something(x)). Ten przykład ilustruje moc generatorów: jeśli cokolwiek jest iteratorem, generator może zapisać stan swojej iteracji.

Aby powtórzyć: Kontynuacje mogą zapisać stan stosu programu, a generatory mogą zapisać stan iteracji. Oznacza to, że kontynuacje są o wiele potężniejsze niż generatory, ale także, że generatory są dużo łatwiejsze. Są one łatwiejsze do wdrożenia przez projektanta języka i są łatwiejsze w użyciu dla programisty (jeśli masz trochę czasu na nagrywanie, spróbuj przeczytać i zrozumieć ta strona o kontynuacjach i wywołaniu /cc ).

Ale możesz łatwo zaimplementować (i konceptualizować) generatory jako prosty, konkretny przypadek stylu przekazywania kontynuacji:

Ilekroć wywoływana jest yield, mówi ona funkcji, aby zwrócić kontynuację. Kiedy functjon jest ponownie wywoływany, zaczyna się od miejsca, w którym został przerwany. Tak więc w pseudo-pseudokodzie (tj. Nie pseudokodzie, ale nie w kodzie) metoda next generatora jest zasadniczo następująca:

 
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

gdzie słowo kluczowe yield jest w rzeczywistości syntaktycznym cukrem dla rzeczywistej funkcji generatora, zasadniczo czymś w rodzaju:

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

Pamiętaj, że jest to tylko pseudokod, a rzeczywista implementacja generatorów w Pythonie jest bardziej złożona. Ale jako ćwiczenie, aby zrozumieć, co się dzieje, spróbuj użyć stylu przekazywania kontynuacji, aby zaimplementować obiekty generatora bez użycia słowa kluczowego yield.

    
140
2018-05-20 10: 25: 32Z

Oto przykład w prostym języku. Zapewnię korespondencję między wysokopoziomowymi pojęciami ludzkimi a niskopoziomowymi koncepcjami Pythona.

Chcę operować sekwencją liczb, ale nie chcę zawracać sobie głowy tworzeniem tej sekwencji, chcę tylko skupić się na operacji, którą chcę wykonać. Robię więc:

  • Dzwonię do ciebie i mówię ci, że chcę sekwencję liczb, która jest produkowana w określony sposób, i dam ci znać, jaki jest algorytm.
    Ten krok odpowiada def wprowadzeniu funkcji generatora, tj. funkcji zawierającej yield.
  • Jakiś czas później mówię ci „OK, przygotuj się, by powiedzieć mi sekwencję liczb”.
    Ten krok odpowiada wywołaniu funkcji generatora, która zwraca obiekt generatora. Zwróć uwagę, że nie powiesz mi jeszcze żadnych liczb; po prostu łapiesz papier i ołówek.
  • Pytam cię, „powiedz mi następny numer”, a ty powiesz mi pierwszy numer; po tym zaczekasz, aż zapytam cię o następny numer. Twoim zadaniem jest pamiętać, gdzie byłeś, jakie liczby już powiedziałeś i jaka jest kolejna liczba. Nie dbam o szczegóły.
    Ten krok odpowiada wywołaniu .next() na obiekcie generatora.
  • … powtórz poprzedni krok, aż…
  • w końcu możesz dojść do końca. Nie powiesz mi numeru; po prostu krzyczysz „trzymaj swoje konie! Skończyłem! Nigdy więcej numerów!”
    Ten krok odpowiada obiektowi generatora kończącemu jego zadanie i wywołując wyjątek StopIteration Funkcja generatora nie musi zgłaszać wyjątku. Jest wywoływany automatycznie, gdy funkcja się kończy lub pojawia się return.

Tak robi generator (funkcja, która zawiera yield); rozpoczyna wykonywanie, zatrzymuje się, gdy robi yield, a gdy zostanie poproszony o wartość .next(), kontynuuje od punktu, w którym był ostatni. Doskonale pasuje do projektu z protokołem iteratora Pythona, który opisuje, jak sekwencyjnie żądać wartości.

Najbardziej znanym użytkownikiem protokołu iteratora jest polecenie for w Pythonie. Tak więc, gdy robisz:

 
for item in sequence:

nie ma znaczenia, czy sequence jest listą, łańcuchem, słownikiem lub generatorem obiektu , jak opisano powyżej; wynik jest taki sam: czytasz pozycje po sekwencji jeden po drugim.

Zauważ, że wpisanie def funkcji zawierającej słowo kluczowe yield nie jest jedynym sposobem na utworzenie generatora; to najłatwiejszy sposób na jego utworzenie.

Aby uzyskać dokładniejsze informacje, przeczytaj o typach iteratorów , yield statement i generatory w dokumentacji Pythona.

    
126
2018-05-20 10: 06: 05Z

Podczas gdy wiele odpowiedzi pokazuje, dlaczego użyłeś yield do stworzenia generatora, jest więcej zastosowań dla yield. Łatwo jest utworzyć koronkę, która umożliwia przekazywanie informacji między dwoma blokami kodu. Nie powtórzę żadnego z drobnych przykładów, które zostały już podane na temat wykorzystania yield do utworzenia generatora.

Aby pomóc zrozumieć, co yield robi w poniższym kodzie, możesz użyć palca do śledzenia cyklu za pomocą dowolnego kodu, który ma yield. Za każdym razem, gdy palec uderza w yield, musisz poczekać na wpisanie next lub send. Gdy wywoływany jest kod next, śledzisz kod, dopóki nie uderzysz w yield… kod po prawej stronie yield jest oceniany i zwracany do dzwoniącego… wtedy czekasz. Po ponownym wywołaniu next wykonujesz kolejną pętlę przez kod. HoZauważ, że w coroutine, yield może być również używany z send…, który wyśle ​​wartość z dzwoniącego do funkcji ustępowania. Jeśli podano send, to yield odbiera wysłaną wartość i wypluwa ją z lewej strony… wtedy ślad w kodzie postępuje, aż ponownie uderzysz w yield (zwracając wartość na końcu, jak gdyby next został wywołany).

Na przykład:

 
>>> 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()
    
110
2014-02-04 02: 27: 35Z
  1. Cute! trampolina (w sensie Lisp). Nie często ich widać!
    2015-12-04 18: 31: 17Z

Istnieje inne użycie i znaczenie yield (od wersji Python 3.3):

 
yield from <expr>

Od PEP 380 - Składnia delegowania do generatora :

  

Proponowana jest składnia generatora, który deleguje część swoich operacji do innego generatora. Pozwala to na rozłożenie na części kodu zawierającego „wydajność” i umieszczenie go w innym generatorze. Ponadto generator może powrócić z wartością, a wartość jest udostępniana generatorowi delegowania.

     

Nowa składnia otwiera również pewne możliwości optymalizacji, gdy jeden generator przywraca wartości wytworzone przez inny.

Ponadto to wprowadzi (od wersji Python 3.5):

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

, aby uniknąć pomyłek w zwykłym generatorze (obecnie w obu jest używane yield).

    
102
2018-05-20 10: 34: 03Z

Wszystkie świetne odpowiedzi, choć trochę trudne dla początkujących.

Zakładam, że nauczyłeś się instrukcji return.

Jako analogię return i yield są bliźniakami. return oznacza „powrót i zatrzymanie”, podczas gdy „wydajność” oznacza „powrót, ale kontynuuj”

  
  1. Spróbuj uzyskać numer_listy z return.
  2.   
 
def num_list(n):
    for i in range(n):
        return i

Uruchom:

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

Zobacz, otrzymujesz tylko jeden numer, a nie ich listę. return nigdy nie pozwala zwyciężyć szczęśliwie, po prostu implementuje się raz i zamyka.

  
  1. Przychodzi yield
  2.   

Zastąp return 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]

Teraz wygrywasz, aby uzyskać wszystkie liczby.

W porównaniu do return, który działa raz i zatrzymuje się, yield razy, które zaplanowałeś. Możesz zinterpretować return jako return one of them i yield jako return all of them. Nazywa się to iterable.

  
  1. Jeszcze jeden krok możemy przepisać instrukcję yield za pomocą return
  2.   
 
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]

To rdzeń około yield.

Różnica między listą return wyjść a wynikiem yield obiektu jest:

Zawsze otrzymasz [0, 1, 2] z obiektu listy, ale tylko raz możesz je pobrać z „wyjścia yield obiektu”. Tak więc ma nową nazwę generator obiektu wyświetlaną w Out[11]: <generator object num_list at 0x10327c990>.

Podsumowując, jako metafora grokowania:

  •  return i yield to bliźniaki
  •  list i generator to bliźniaki
88
2018-05-28 09: 06: 22Z
  1. To jest zrozumiałe, ale jedną z głównych różnic jest to, że możesz mieć wiele wydajności w funkcji /metodzie. Analogia całkowicie się załamuje w tym momencie. Wydajność zapamiętuje swoje miejsce w funkcji, więc przy następnym wywołaniu next () funkcja będzie kontynuowana do następnego yield. Myślę, że to ważne i powinno być wyrażone.
    2018-08-23 13: 27: 21Z

Oto kilka przykładów Pythona, jak faktycznie zaimplementować generatory, tak jakby Python nie dostarczył im cukru syntaktycznego:

Jako generator Pythona:

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

Używanie zamknięć leksykalnych zamiast generatorów

 
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)

Używanie zamknięć obiektów zamiast generatorów (ponieważ 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)
    
86
2017-10-24 10: 46: 05Z

Zamierzałem opublikować „przeczytaj stronę 19„ Python: Essential Reference ”Beazleya, aby uzyskać szybki opis generatorów”, ale wielu innych już opublikowało dobre opisy.

Należy również zauważyć, że yield może być używany w coroutines jako podwójny ich zastosowanie w funkcjach generatora. Chociaż nie jest to takie samo zastosowanie, jak fragment kodu, (yield) może być używany jako wyrażenie w funkcji. Gdy program wywołujący wysyła wartość do metody przy użyciu metody send(), wówczas operacja wykonywana jest aż do napotkania następnej instrukcji (yield).

Generatory i generatory są świetnym sposobem na konfigurowanie aplikacji typu przepływu danych. Pomyślałem, że warto wiedzieć o innym użyciu instrukcji yield w funkcjach.

    
82
2013-01-28 01: 37: 10Z

Z punktu widzenia programowania iteratory są zaimplementowane jako gratki .

Aby zaimplementować iteratory, generatory i pule wątków do współbieżnego wykonywania itp. jako tiki (zwane również funkcjami anonimowymi), używa się wiadomości wysyłanych do obiektu zamknięcia, który ma dyspozytora, a dyspozytor odpowiada na „wiadomości”.

http://en.wikipedia.org/wiki/Message_passing

next ” to wiadomość wysłana do zamknięcia utworzona przez wywołanie „ iter ”.

Istnieje wiele sposobów implementacji tego obliczenia. Użyłem mutacji, ale łatwo jest to zrobić bez mutacji, zwracając aktualną wartość i następnego yieldera.

Oto demonstracja wykorzystująca strukturę R6RS, ale semantyka jest całkowicie identyczna z semantyką Pythona. Jest to ten sam model obliczeniowy i tylko zmiana składni jest wymagana do przepisania go w Pythonie.

 
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
->
    
80
2018-05-20 10: 29: 34Z

Oto prosty przykład:

 
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)

Wyjście:

 
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

Nie jestem programistą Pythona, ale wygląda na to, że yield przechowuje pozycję przepływu programu, a następna pętla zaczyna się od pozycji „wydajność”. Wygląda na to, że czeka w tej pozycji i tuż przed tym, zwracając wartość na zewnątrz, a następnym razem nadal działa.

Wydaje się, że jest to interesująca i fajna umiejętność: D

    
72
2018-05-20 10: 31: 01Z
  1. Nie zapominaj, że 2 jest liczbą pierwszą :-)
    2018-07-02 01: 20: 23Z
  2. Masz rację. Ale jaki jest wpływ na przepływ, który polega na zachowaniu „wydajności”? Mogę zmienić algorytm w imię matematyki. Czy pomoże to uzyskać inną ocenę „wydajności”?
    2018-07-02 01: 44: 08Z

Oto mentalny obraz tego, co robi yield.

Lubię myśleć o wątku jako o stosie (nawet jeśli nie jest zaimplementowany w ten sposób).

Gdy wywoływana jest normalna funkcja, umieszcza lokalne zmienne na stosie, wykonuje pewne obliczenia, a następnie czyści stos i zwraca. Wartości lokalnych zmiennych nigdy nie są ponownie widoczne.

Z funkcją yield, gdy jej kod zaczyna działać (tj. po wywołaniu funkcji, powrótobiekt generatora, którego metoda next() jest następnie wywoływana), podobnie umieszcza swoje lokalne zmienne na stosie i oblicza na chwilę. Ale wtedy, gdy trafi w instrukcję yield, przed wyczyszczeniem jej części stosu i zwróceniem, pobiera migawkę swoich zmiennych lokalnych i zapisuje je w obiekcie generatora. Zapisuje także miejsce, w którym aktualnie się znajduje, w swoim kodzie (tj. W konkretnej instrukcji yield).

Jest to więc rodzaj zamrożonej funkcji, na której generuje generator.

Kiedy next() zostanie wywołany później, pobiera rzeczy na stosie i ponownie je animuje. Funkcja kontynuuje obliczanie od miejsca, w którym została przerwana, nieświadoma faktu, że właśnie spędził wieczność w chłodni.

Porównaj następujące przykłady:

 
def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Gdy wywołujemy drugą funkcję, zachowuje się ona zupełnie inaczej niż pierwsza. Instrukcja yield może być nieosiągalna, ale jeśli jest obecna w dowolnym miejscu, zmienia naturę tego, z czym mamy do czynienia.

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

Wywołanie yielderFunction() nie uruchamia swojego kodu, ale powoduje usunięcie generatora z kodu. (Być może dobrym pomysłem jest nazwanie takich rzeczy przedrostkiem yielder dla czytelności.)

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

Pola gi_code i gi_frame są miejscem przechowywania stanu zamrożenia. Eksplorując je za pomocą dir(..), możemy potwierdzić, że nasz model mentalny powyżej jest wiarygodny.

    
59
2017-03-01 13: 36: 58Z

Jak sugeruje każda odpowiedź, yield służy do tworzenia generatora sekwencji. Służy do dynamicznego generowania sekwencji. Na przykład, podczas czytania pliku po linii w sieci, możesz użyć funkcji yield w następujący sposób:

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

Możesz go użyć w swoim kodzie w następujący sposób:

 
for line in getNextLines():
    doSomeThing(line)

Przekazanie kontroli wykonania getcha

Kontrola wykonania zostanie przeniesiona z getNextLines () do pętli for po wykonaniu wydajności. Tak więc za każdym razem, gdy wywoływane jest getNextLines (), wykonywanie rozpoczyna się od momentu, w którym zostało wstrzymane ostatnim razem.

Krótko mówiąc, funkcja z następującym kodem

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

for i in simpleYield():
    print i

wydrukuje

 
"first time"
"second time"
"third time"
"Now some useful value 12"
    
50
2018-05-20 10: 42: 59Z

Wydajność to obiekt

A return w funkcji zwróci pojedynczą wartość.

Jeśli chcesz, aby funkcja zwróciła ogromny zestaw wartości , użyj yield.

Co ważniejsze, yield to bariera .

  

jak bariera w języku CUDA, nie przeniesie kontroli, dopóki nie zostanie uzyskana   zakończone.

To znaczy, uruchomi kod w twojej funkcji od początku, aż osiągnie yield. Następnie zwróci pierwszą wartość pętli.

Następnie każde inne wywołanie uruchomi pętlę, którą jeszcze raz zapisałeś w funkcji, zwracając następną wartość, aż nie będzie żadnej wartości do zwrócenia.

    
44
2018-05-20 10: 45: 50Z

(Moja poniższa odpowiedź mówi tylko z perspektywy używania generatora Pythona, a nie podstawowa implementacja mechanizmu generującego , która obejmuje pewne sztuczki manipulacji stosem i stertą.)

Gdy yield jest używane zamiast return w funkcji Pythona, funkcja ta jest przekształcana w coś specjalnego o nazwie generator function. Ta funkcja zwróci obiekt typu generator. Słowo kluczowe yield to flaga powiadamiająca kompilator Pythona o specjalnym traktowaniu takiej funkcji. Normalne funkcje zostaną zakończone po zwróceniu wartości. Ale z pomocą kompilatora funkcję generatora można uznać za wznawiającą. Oznacza to, że kontekst wykonania zostanie przywrócony, a wykonanie będzie kontynuowane od ostatniego uruchomienia. Dopóki nie wywołasz jawnie wywołania return, który wywoła wyjątek StopIteration (który jest również częścią protokołu iteratora) lub dotrze do końca funkcji. Znalazłem wiele referencji dotyczących generator, ale to jeden z functional programming perspective jest najbardziej strawny.

(Teraz chcę porozmawiać o uzasadnieniu generator i iterator w oparciu o moje własne zrozumienie. Mam nadzieję, że pomoże to zrozumieć podstawową motywację iteratora i generator. Taka koncepcja pojawia się również w innych językach, takich jak C #.)

Jak rozumiem, gdy chcemy przetworzyć kilka danych, zwykle najpierw przechowujemy dane gdzieś, a następnie przetwarzamy je pojedynczo. Ale to naiwne podejście jest problematyczne. Jeśli ilość danych jest ogromna, przechowywanie ich jako całości jest kosztowne. Więc zamiast przechowywać bezpośrednio data, dlaczego nie przechowywać jakiegoś rodzaju metadata pośrednio, tj. the logic how the data is computed .

Istnieją dwa podejścia do zawijania takich metadanych.

  1. Podejście OO, zawijamy metadane as a class. Jest to tak zwany iterator, który implementuje protokół iteratora (tj. Metody __next__() i __iter__()). Jest to również powszechnie widziany wzorzec projektowania iteratora .
  2. Podejście funkcjonalne, zawijamy metadane as a function. To jest tak zwany generator function. Ale pod maską zwrócona generator object nadal IS-A iterator, ponieważ implementuje również protokół iteratora.

Tak czy inaczej, tworzony jest iterator, tj. jakiś obiekt, który może dać ci dane, które chcesz. Podejście OO może być nieco skomplikowane. W każdym razie, którego użyć, zależy od ciebie.

    
44
2018-11-23 01: 38: 59Z

Podsumowując, instrukcja yield przekształca twoją funkcję w fabrykę, która produkuje specjalny obiekt o nazwie generator, który otacza ciało twojej oryginalnej funkcji. Gdy generator jest iterowany, wykonuje twoją funkcję, dopóki nie osiągnie następnego yield, a następnie zawiesza wykonanie i ocenia na wartość przekazaną do yield. Powtarza ten proces przy każdej iteracji, aż ścieżka wykonania zakończy działanie. Na przykład

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

for i in simple_generator():
    print i

po prostu wydruki

 
one
two
three

Moc pochodzi z użycia generatora z pętlą, która oblicza sekwencję, generator wykonuje pętlę zatrzymując się za każdym razem, aby „uzyskać” następny wynik obliczeń, w ten sposób oblicza listę w locie, korzyść będąc pamięcią zapisaną dla szczególnie dużych obliczeń

Powiedzmy, że chciałeś stworzyć własną funkcję range, która tworzy powtarzalny zakres liczb, możesz to zrobić tak,

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

i używaj go w ten sposób;

 
for i in myRangeNaive(10):
    print i

Ale to jest nieefektywne, ponieważ

  • Tworzysz tablicę, której używasz tylko raz (to marnuje pamięć)
  • Ten kod faktycznie zapętla tę tablicę dwa razy! :(

Na szczęście Guido i jego zespół byli wystarczająco hojni, aby opracować generatory, abyśmy mogli to zrobić;

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

for i in myRangeSmart(10):
    print i

Teraz po każdej iteracji funkcja generatora o nazwie next() wykonuje tę funkcję, dopóki nie osiągnie instrukcji „yield”, w której zatrzymuje się i „daje” wartość lub osiąga koniec funkcji. W tym przypadku przy pierwszym wywołaniu next() wykonuje aż do instrukcji yield i daje 'n', podczas następnego wywołania wykonuje instrukcję inkrementacji, przeskakuje z powrotem do 'while', ocenia ją, a jeśli jest prawdziwa, zatrzyma się i powtórz 'n', będzie to kontynuowane tak długo, aż warunek while zwróci false, a generator przeskoczy na koniec funkcji.

    
43
2018-05-20 11: 04: 36Z

Wiele osób używa return zamiast yield, ale w niektórych przypadkach yield może być bardziej wydajny i łatwiejszy w obsłudze.

Oto przykład, który yield jest zdecydowanie najlepszy:

  

powrót (w funkcji)

 
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
  

wydajność (w funkcji)

 
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.
  

Funkcje dzwonienia

 
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)

Obie funkcje robią to samo, ale yield używa trzech linii zamiast pięciu i ma jedną mniejszą zmienną, o którą należy się martwić.

  
    

To jest wynik kodu:

  

41

2018-05-20 11: 02: 52Z

yield jest jak element powrotu dla funkcji. Różnica polega na tym, że element yield zamienia funkcję w generator. Generator zachowuje się jak funkcja, dopóki coś nie zostanie „wygenerowane”. Generator zatrzymuje się, dopóki nie zostanie wywołany, i kontynuuje od dokładnie tego samego punktu, w którym się rozpoczął. Możesz uzyskać sekwencję wszystkich „uzyskanych” wartości w jednym, dzwoniąc pod numer list(generator()).

    
36
2015-05-20 06: 19: 32Z

Słowo kluczowe yield po prostu zbiera powracające wyniki. Pomyśl o yield jak return +=

    
35
2016-04-23 03: 16: 19Z

Oto proste podejście oparte na yield, aby obliczyć serię fibonacci, wyjaśnione:

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

Kiedy wpiszesz to do swojego REPL, a następnie spróbujesz zadzwonić, otrzymasz tajemniczy wynik:

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

To dlatego, że obecność yield zasygnalizowała Pythonowi, że chcesz utworzyć generator , czyli obiekt generujący wartości na żądanie.

Jak więc generujesz te wartości? Można to zrobić bezpośrednio za pomocą wbudowanej funkcji next lub, pośrednio, przez podanie jej do konstrukcji, która pobiera wartości.

Korzystając z wbudowanej funkcji next(), wywołujesz bezpośrednio .next/__next__, zmuszając generator do wygenerowania wartości:

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

Pośrednio, jeśli podasz fib do pętli for, inicjatora list, inicjatora tuple lub czegokolwiek innego, co oczekuje obiektu, który generuje /wytwarza wartości, „zużyjesz” generator, dopóki nie będzie można wygenerować więcej wartości przez to (i wraca):

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

Podobnie z inicjatorem tuple:

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

Generator różni się od funkcji w tym sensie, że jest leniwy. Osiąga to, utrzymując stan lokalny i umożliwiając wznowienie pracy w dowolnym momencie.

Kiedy po raz pierwszy wywołujesz fib, wywołując go:

 
f = fib()

Python kompiluje funkcję, napotyka słowo kluczowe yield i po prostu zwraca z powrotem obiekt generatora. Wydaje się niezbyt pomocne.

Kiedy następnie zażądasz wygenerowania pierwszej wartości, bezpośrednio lub pośrednio, wykonuje wszystkie stwierdzenia, które znajdzie, dopóki nie napotka yield, a następnie zwróci wartość dostarczoną do yield i wstrzyma. Dla przykładu, który lepiej to pokazuje, użyjmy około print wywołań (zastąp print "text" jeśli w Pythonie 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.")

Teraz wpisz REPL:

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

masz obiekt generatora, który oczekuje na polecenie, aby wygenerować wartość. Użyj next i zobacz, co zostało wydrukowane:

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

Wyniki nienotowane są drukowane. Cytowany wynik jest zwracany z yield. Zadzwoń ponownie next teraz:

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

Generator pamięta, że ​​został wstrzymany przy yield value i wznawia stamtąd. Zostanie wydrukowana następna wiadomość i poszukiwanie instrukcji yield, aby zatrzymać ją ponownie (z powodu pętli while).

    
32
2017-07-12 12: 44: 24Z

Prosty przykład, aby zrozumieć, co to jest: yield

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


for i in f123():
    print i

Dane wyjściowe to:

 
1 2 1 2 1 2 1 2
    
32
2019-06-06 12: 08: 26Z
źródło umieszczone tutaj