Pytanie Co oznacza słowo kluczowe "yield"?


Jaki jest pożytek z yield słowo kluczowe w Pythonie? Co to robi?

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

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  

A 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 metoda _get_child_candidates jest nazywany? Czy zwrócono listę? Pojedynczy element? Czy nazywa się to ponownie? Kiedy kolejne rozmowy będą przerywane?


1. Kod pochodzi od Jochen Schulz (jrschulz), który stworzył wspaniałą bibliotekę Pythona dla przestrzeni metrycznych. To jest link do pełnego źródła: Mspace modułu.


8325
2017-10-23 22:21


pochodzenie




Odpowiedzi:


Aby zrozumieć, co yield robi, musisz zrozumieć co generatory są. I zanim pojawią się generatory iterables.

Iterables

Gdy tworzysz listę, możesz czytać jej elementy jeden po drugim. Czytanie jej pozycji jeden po drugim nazywa się iteracją:

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

mylist to jest iterable. Kiedy używasz rozumienia list, tworzysz listę, a więc iterowalną:

>>> 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..."on jest iterable; lists, strings, akta...

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

Generatory

Generatory to iteratory, rodzaj iteracji możesz tylko powtórzyć 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

To jest to samo, z wyjątkiem tego, którego użyłeś () zamiast []. Ale ty nie może wykonać for i in mygenerator drugi raz, ponieważ generatory mogą być użyte tylko raz: obliczają 0, potem zapominają o tym i obliczają 1, a kończą obliczanie 4, jeden po drugim.

Wydajność

yield jest słowem kluczowym, 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 to bezużyteczny przykład, ale przydaje się, gdy wiesz, że funkcja zwróci ogromny zestaw wartości, które będziesz musiał przeczytać tylko raz.

Opanować yieldmusisz to zrozumieć po wywołaniu funkcji kod napisany w treści funkcji nie działa. Funkcja zwraca tylko obiekt generatora, co jest nieco trudne :-)

Następnie twój kod będzie uruchamiany za każdym razem for używa generatora.

Teraz trudna część:

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

Generator jest uważany za pusty, gdy funkcja działa, ale nie trafia yield już. Może tak być, ponieważ pętla dobiegła końca lub dlatego, że nie spełniasz "if/else" już.


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

Gość:

# 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 na przeglądanie wszystkich zagnieżdżonych danych, nawet jeśli jest to trochę niebezpieczne, ponieważ możesz skończyć z nieskończoną pętlą. W tym przypadku, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) wyczerpuje wszystkie wartości generatora, ale while kontynuuje tworzenie nowych obiektów generujących, które będą generować różne wartości od poprzednich, ponieważ nie są one stosowane w tym samym węźle.

  • The extend() Metoda to metoda obiektu listy, która oczekuje wartości iterowalnej i dodaje jej wartości do listy.

Zazwyczaj podajemy do niego 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 czytać wartości dwukrotnie.
  2. Możesz mieć dużo dzieci i nie chcesz, aby wszystkie były przechowywane w pamięci.

I działa, ponieważ Python nie dba o to, czy argument metody jest listą czy nie. Python oczekuje iterables, więc będzie działać z ciągami, listami, krotkami i generatorami! Nazywa się to kaczym pisaniem i jest jednym z powodów, dla których Python jest taki fajny. Ale to już inna historia, na kolejne pytanie ...

Możesz zatrzymać się tutaj lub przeczytać trochę, aby zobaczyć zaawansowane wykorzystanie generatora:

Kontrolowanie wyczerpania 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żyjprint(corner_street_atm.__next__()) lub print(next(corner_street_atm))

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

Itertools, twój najlepszy przyjaciel

Moduł itertools zawiera specjalne funkcje do manipulowania iterables. Czy chcesz kiedykolwiek powielać generator? Łańcuch dwóch generatorów? Grupuje wartości w zagnieżdżonej liście z jedną linią? Map / Zip bez tworzenia kolejnej listy?

Więc po prostu import itertools.

Przykład? Zobaczmy możliwe zlecenia 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 iterables (implementacja __iter__() metoda) i iteratory (wdrażające __next__() metoda). Iterables to dowolne obiekty, z których można uzyskać iterator. Iteratory to obiekty, które umożliwiają iterację iterabli.

Więcej na ten temat w tym artykule w jaki sposób for pętle działają.


12189
2017-10-23 22:48



Wszystko Iteratory mogą być iterowane tylko raz, nie tylko te generowane przez funkcje generatora. Jeśli mi nie wierzysz, zadzwoń iter() na dowolnym iterowanym obiekcie i spróbuj powtórzyć wynik więcej niż raz. - augurar
@Craicerjack Masz swoje warunki zmieszane. Iterable to coś z __iter__ metoda. Iterator jest wynikiem połączenia iter() na iterable. Iteratory mogą być iterowane tylko raz. - augurar
yield nie jest tak magiczny, jak sugeruje ta odpowiedź. Kiedy wywołujesz funkcję zawierającą yield w dowolnym miejscu, otrzymujesz obiekt generatora, ale nie działa kod. Następnie za każdym razem, gdy wyodrębni się obiekt z generatora, Python wykonuje kod w funkcji, aż dojdzie do yield oświadczenie, a następnie zatrzymuje i dostarcza obiekt. Po wypakowaniu innego obiektu, Python wznawia się zaraz po yieldi trwa, dopóki nie osiągnie kolejnego yield (często ta sama, ale jedna iteracja później). Trwa to do momentu, gdy funkcja zakończy się na końcu, w którym to momencie generator zostanie uznany za wyczerpany. - Matthias Fripp
@MatthiasFripp wskazał dokładnie, co sprawiło, że potknąłem się czytając tę ​​odpowiedź. "Twój kod będzie uruchamiany za każdym razem" jest błędnym stwierdzeniem. "Twój kod będzie kontynuowany od miejsca, w którym zostało przerwane" jest dokładniejszym sposobem na wyrażenie go. - Indigenuity
"Te iterables są przydatne ... ale przechowujesz wszystkie wartości w pamięci i to nie zawsze jest to, czego chcesz", jest błędne lub mylące. Iterowalna zwraca iterator po wywołaniu iter () na iterable, a iterator nie zawsze musi przechowywać swoje wartości w pamięci, w zależności od implementacji iter Metoda ta może również generować wartości w sekwencji na żądanie. - picmate 涅


Skrót do Grokowanie  yield

Gdy zobaczysz funkcję z yield oświadczenia, zastosuj tę prostą sztuczkę, aby zrozumieć, co się stanie:

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

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

Nie należy mylić swoich Iterables, Iteratorów i Generatorów

Po pierwsze protokół iteratora - kiedy piszesz

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

Python wykonuje następujące dwa kroki:

  1. Pobiera iterator dla mylist:

    Połączenie iter(mylist) -> to zwraca obiekt z next() metoda (lub __next__() w Pythonie 3).

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

  2. Używa iteratora do zapętlenia elementów:

    Kontynuuj dzwonienie next() metoda na iteratorze zwrócona z kroku 1. Wartość zwracana z next() jest przypisany do x a korpus pętli zostanie wykonany. Jeśli wyjątek StopIteration jest podniesiony od wewnątrz next()oznacza to, że w iteratorze nie ma więcej wartości, a pętla zostaje zakończona.

Prawda jest taka, że ​​Python wykonuje powyższe dwa kroki, kiedy tylko chce pętla zawartość obiektu - może to być pętla for, ale może być również podobna do kodu otherlist.extend(mylist) (gdzie otherlist jest listą Pythona).

Tutaj mylist to jest iterable ponieważ implementuje protokół iteratora. W klasie zdefiniowanej przez użytkownika możesz zaimplementować __iter__() metoda, aby instancje klasy były iterowalne. Ta metoda powinna zwrócić wartość iterator. Iterator jest obiektem z next() metoda. Możliwe jest zaimplementowanie obu __iter__() i next() na tej samej klasie i mieć __iter__() powrót self. Będzie to działać w prostych przypadkach, ale nie wtedy, gdy chcesz, aby dwa iteratory przepętały się nad tym samym obiektem w tym samym czasie.

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

  1. Wbudowane listy, słowniki, krotki, zestawy, pliki.
  2. Zdefiniowane przez użytkownika klasy implementujące __iter__().
  3. Generatory.

Zauważ, że a for Pętla nie wie, z jakim obiektem ma do czynienia - jest po prostu zgodna z protokołem iteratora i jest zadowolona, ​​że ​​otrzyma przedmiot po wywołaniu next(). Wbudowane listy zwracają pozycje jeden po drugim, słowniki zwracają klawiatura jeden po drugim, pliki zwracają kwestia jeden po drugim itd. Powracają generatory ... cóż, to jest miejsce yield wchodzi:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Zamiast yield wypowiedzi, jeśli miałeś trzy return wypowiedzi w f123() tylko pierwszy zostanie wykonany, a funkcja zostanie zakończona. Ale f123() nie jest zwykłą funkcją. Gdy f123() nazywa się to nie zwróć dowolną z wartości w instrukcjach yield! Zwraca obiekt generatora. Ponadto funkcja tak naprawdę nie wychodzi - przechodzi w stan zawieszenia. Kiedy for Pętla próbuje pętli nad obiektem generatora, funkcja wznawia ze stanu zawieszenia na następnej linii po yield poprzednio zwrócony z, wykonuje następny wiersz kodu, w tym przypadku a yield instrukcja i zwraca ją jako następny element. Zdarza się to do momentu wyjścia funkcji, w którym to momencie generator podnosi się StopIteration, a pętla wychodzi.

Tak więc obiekt generatora jest czymś w rodzaju adaptera - na jednym końcu wykazuje protokół iteratora, poprzez ekspozycję __iter__() i next() metody utrzymania for pętla szczęśliwa. Z drugiej jednak strony uruchamia funkcję tylko na tyle, aby uzyskać kolejną wartość z niej i umieszcza ją z powrotem w trybie zawieszonym.

Dlaczego warto korzystać z generatorów?

Zwykle można napisać kod, który nie używa generatorów, ale implementuje tę samą logikę. Jedną z opcji jest użycie listy tymczasowej "trick", o której wspomniałem wcześniej. To nie zadziała we wszystkich przypadkach, np. jeśli masz nieskończone pętle lub możesz nieefektywnie korzystać z pamięci, gdy masz naprawdę długą listę. Innym podejściem jest implementacja nowej klasy iterowalnej SomethingIter utrzymujące stan w elementach instancji i wykonujące kolejny logiczny krok w jego obrębie next() (lub __next__() w Pythonie 3). W zależności od logiki, kod wewnątrz next() metoda może okazać się bardzo skomplikowana i podatna na błędy. Tutaj generatory zapewniają czyste i łatwe rozwiązanie.


1638
2017-10-25 21:22



"Gdy zobaczysz funkcję z deklaracjami zysku, zastosuj tę łatwą sztuczkę, aby zrozumieć, co się stanie" Czy to całkowicie nie zignoruje faktu, że możesz send do generatora, który jest ogromną częścią punktu generatorów? - DanielSank
"może to być pętla for, ale może to być również kod otherlist.extend(mylist)"-> To jest nieprawidłowe. extend() modyfikuje listę na miejscu i nie zwraca wartości iterowalnej. Próbuje się zapętlić otherlist.extend(mylist) zawiedzie z a TypeError bo extend() niejawnie zwraca Nonei nie można się zapętlić None. - Pedro
@pedro Źle zrozumiałeś to zdanie. Oznacza to, że python wykonuje dwa wspomniane kroki mylist (nie na otherlist) podczas wykonywania otherlist.extend(mylist). - today


Pomyśl o tym w ten sposób:

Iterator jest po prostu fantazyjnym terminem brzmiącym dla obiektu, który ma metodę next (). Tak więc funkcja yield-ed kończy się czymś w rodzaju:

Orginalna wersja:

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

for i in some_function():
    print i

Zasadniczo to, co robi interpreter Pythona 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, for Pętla może być przepisana do tego:

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

Czy to ma więcej sensu, czy po prostu wprowadza więcej zamieszania? :)

Powinienem to zauważyć jest uproszczenie w celach ilustracyjnych. :)


397
2017-10-23 22:28



__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), Drukuje: 0, 10, 20, ..., 90 - jfs
Próbowałem tego przykładu w Pythonie 3.6 i jeśli tworzę iterator = some_function(), zmienna iterator nie ma funkcji o nazwie next() już, ale tylko a __next__() funkcjonować. Pomyślałem, że o tym wspomnę. - Peter


The yield słowo kluczowe jest zredukowane do dwóch prostych faktów:

  1. Jeśli kompilator wykryje yield słowo kluczowe gdziekolwiek wewnątrz funkcji, funkcja ta już nie powraca za pośrednictwem return komunikat. Zamiast, to natychmiast zwraca a obiekt "oczekującej listy" nazywany generatorem
  2. Generator jest iterowalny. Co jest iterable? To coś jak list lub set lub range lub dyktafon, z wbudowany protokół do odwiedzania każdego elementu w określonej kolejności.

W skrócie: generator jest leniwą, inkrementującą się listą, i yield instrukcje pozwalają na używanie notacji funkcji do programowania wartości list generator powinien stopniowo wypluwać.

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 to tak jak w Pythonie range. Powołanie makeRange(n) ZWRACA 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>

Aby zmusić generator do natychmiastowego zwrotu jego oczekujących wartości, możesz go przekazać list() (tak jak można to zrobić dowolnie):

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

Porównywanie przykładu do "tylko zwrócenie listy"

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

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

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


Jak możesz używać generatorów

Iterowalna to ostatnia część rozumienia listy, a wszystkie generatory są iterowalne, więc są często używane w następujący sposób:

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

Aby uzyskać lepsze wyczucie generatorów, możesz bawić się z itertools moduł (pamiętaj, aby użyć chain.from_iterable zamiast chain gdy jest to uzasadnione). Na przykład, możesz nawet użyć generatorów do zaimplementowania nieskończenie długich leniwych list, takich jak itertools.count(). Możesz wdrożyć własne def enumerate(iterable): zip(count(), iterable)lub alternatywnie zrobić to za pomocą yieldsłowo kluczowe w pętli while.

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


Za kulisami

Tak działa "protokół iteracyjny Pythona". To znaczy, co się dzieje, kiedy to robisz list(makeRange(5)). To właśnie opisałem wcześniej jako "leniwą, przyrostową listę".

>>> 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 obiekty .next() funkcja, która jest częścią "protokołu iteracji" i znajduje się we wszystkich iteratorach. Możesz ręcznie użyć next() funkcja (i inne części protokołu iteracji) do realizacji fantazyjnych rzeczy, zwykle kosztem czytelności, więc staraj się tego unikać ...


Drobne szczegóły

Zwykle większość ludzi nie dba o następujące rozróżnienia i prawdopodobnie chce przestać czytać tutaj.

W języku Python: iterable jest dowolnym obiektem, który "rozumie pojęcie pętli for", jak lista [1,2,3]i iterator jest specyficzną instancją żądanej pętli for [1,2,3].__iter__(). ZA 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że, gdy zażądasz iteratora z iteratora (który rzadko byś zrobił), to po prostu daje ci kopię samego siebie.

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

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

... potem pamiętaj, że generator to iterator; to znaczy, jest to jednorazowe użycie. Jeśli chcesz go ponownie użyć, zadzwoń myRange(...) jeszcze raz. Jeśli chcesz użyć wyniku dwukrotnie, 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 hackowe metaprogramowanie) mogą użyć itertools.tee jeśli jest to absolutnie konieczne, od kopiowalnego iteratora Python WERWA Propozycja dotycząca standardów została odroczona.


348
2018-06-19 06:33





Co robi yield słowo kluczowe czy w Pythonie?

Odpowiedz Zarys / Podsumowanie

  • Funkcja z yield, gdy zostanie wywołany, zwraca a Generator.
  • Generatory są iteratorami, ponieważ implementują one protokół iteratora, więc możesz powtórzyć je.
  • Może też być generator wysłał informacje, czyniąc to koncepcyjnie a Coroutine.
  • W Pythonie 3 możesz delegat z jednego generatora na drugi w obu kierunkach z yield from.
  • (Dodatek krytykuje kilka odpowiedzi, w tym pierwszą, i omawia użycie return w generatorze.)

Generatory:

yield jest legalne tylko w definicji funkcji, oraz włączenie yield w definicji funkcji powoduje, że zwraca on generator.

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

yield zapewnia łatwy sposób wdrażanie protokołu iteratora, zdefiniowane przez następujące dwie metody: __iter__ i next (Python 2) lub __next__ (Python 3). Obie te metody uczynić obiekt iteratorem, który można wpisać-sprawdzić za pomocą Iterator Abstrakcyjna baza Klasa od collections moduł.

>>> 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 jest podtypem iteratora:

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

Jeśli zajdzie taka potrzeba, możemy sprawdzić tekst w ten sposób:

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

Cecha Iterator  czy to raz wyczerpany, nie możesz go ponownie użyć ani zresetować:

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

Będziesz musiał wykonać drugą, jeśli chcesz ponownie użyć jej funkcjonalności (patrz przypis 2):

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

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

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

Powyższy prosty generator jest również równoważny z poniższym - jak w Pythonie 3.3 (i niedostę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 subgeneratorów, co zostanie wyjaśnione w następnym rozdziale dotyczącym delegowania spółdzielni z podwykonawcami.

Coroutines:

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

Oto przykład, zwróć uwagę na received zmienna, 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 umieścić w kolejce generator z wbudowaną funkcją, next. To będzie zadzwoń do odpowiedniego next lub __next__ metoda, 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 przesłać dane do generatora. (Wysyłanie None jest to samo co wołanie next.):

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

Delegacja współpracy z Sub-Coroutine z yield from

Przypomnij sobie to yield from jest dostępny w Pythonie 3. Pozwala nam to delegować coroutines do subcoroutine:

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


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

Teraz możemy delegować funkcje do pod-generatora i można z niego korzystać przez generator jak wyż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 o dokładnej semantyki yield from w PEP 380.

Inne metody: zamknij i rzuć

The close metoda podnosi GeneratorExit w punkcie funkcji wykonanie zostało zamrożone. Będzie to również wywoływane przez __del__ więc ty może umieścić dowolny kod porządkowy, w którym będziesz obsługiwał GeneratorExit:

>>> my_account.close()

Możesz również wyrzucić wyjątek, który można obsłużyć w generatorze lub propagowane 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 następującego pytania:

Co robi yield słowo kluczowe czy w Pythonie?

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


Dodatek:

Krytyka najwyższej / zaakceptowanej odpowiedzi **

  • Jest zdezorientowany, co sprawia, że iterable, używając tylko listy jako przykładu. Zobacz moje referencje powyżej, ale w podsumowaniu: Iterable ma __iter__ metoda zwracająca iterator. Na iterator zapewnia .next (Python 2 lub .__next__ (Python 3), która jest domyślnie wywoływana przez forpętle, dopóki się nie podniesie StopIteration, a kiedy już to zrobi, będzie to nadal robić.
  • Następnie używa wyrażenia generatora, aby opisać, czym jest generator. Ponieważ generator jest po prostu wygodnym sposobem na stworzenie iterator, tylko myli sprawę, a my jeszcze nie dotarliśmy do tego yield część.
  • W Kontrolowanie wyczerpania generatora on dzwoni .next metoda, 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 miało to znaczenia yield w ogóle robi.
  • Bez dyskusji na temat metod, które yield zapewnia wraz z nową funkcjonalnością yield from w Pythonie 3. Górna / zaakceptowana odpowiedź jest bardzo niekompletną odpowiedzią.

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

Gramatyka obecnie zezwala na dowolne wyrażenie w zrozumieniu 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 twierdzili, że jest ona interesująca w używaniu jej w wyrażeniach lub ekspresji generatora - pomimo przytaczania niezbyt dobrego przykładu użycia.

Podstawowymi twórcami CPython są dyskutowanie na temat amortyzacji zasiłku. Oto odpowiedni wpis z listy mailingowej:

W dniu 30 stycznia 2017 o 19:05, Brett Cannon napisał:

On Sun, 29 stycznia 2017 o 16:39 Craig Rodrigues napisał:

Jestem w porządku z każdym podejściem. Opuszczanie rzeczy w Pythonie 3       nie jest dobrze, IMHO.

Moim głosowaniem jest generowanie skryptów, ponieważ nie otrzymujesz tego, czego oczekujesz     składnia.

Zgodzę się, że to rozsądne miejsce dla nas, jak każdy kod   poleganie na bieżącym zachowaniu jest naprawdę zbyt sprytne   do utrzymania.

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

  • SyntaxWarning lub DeprecationWarning w 3.7
  • Ostrzeżenie Py3k w 2.7.x
  • SyntaxError w 3.8

Pozdrawiam, Nick.

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

Ponadto istnieje nierozstrzygnięta sprawa (10544) co wydaje się wskazywać w tym kierunku nigdy bycie dobrym pomysłem (PyPy, implementacja Pythona napisana w Pythonie, już ostrzega o składni).

Podsumowując, dopóki twórcy CPython nie powiedzą nam inaczej: Nie wkładaj yield w wyrażeniu lub zrozumieniu generatora.

The return oświadczenie w generatorze

W Python 2:

W funkcji generatora return oświadczenie nie może zawierać znaku expression_list. W tym kontekście nagie return wskazuje, że generator jest gotowy i spowoduje StopIteration być wychowanym.

Na expression_list jest w zasadzie dowolną liczbą wyrażeń oddzielonych przecinkami - zasadniczo w Pythonie 2 można zatrzymać generator return, ale nie możesz zwrócić wartości.

W Python 3:

W funkcji generatora return oświadczenie wskazuje, że generator jest gotowy i spowoduje StopIteration być wychowanym. Zwrócona wartość (jeśli jest) jest używana jako argument do skonstruowania StopIteration i staje się StopIteration.valueatrybut.

Przypisy

  1. Języki CLU, Sather i Icon zostały wymienione we wniosku wprowadzić pojęcie generatorów do Pythona. Ogólna idea jest że funkcja może utrzymywać stan wewnętrzny i wydajność pośrednią punkty danych na żądanie użytkownika. To się zapowiadało lepsza wydajność do innych podejść, w tym do wątków Pythona, który nie jest nawet dostępny w niektórych systemach.

  2.  Oznacza to na przykład, że xrange obiekty (range w Pythonie 3) nie są Iterators, nawet jeśli są iterowalne, ponieważ mogą być ponownie wykorzystane. Podobnie jak listy, ich __iter__ metody zwracają obiekty iteratora.

  3. yield został pierwotnie wprowadzony jako oświadczenie, 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 proponowane aby umożliwić użytkownikowi wysyłanie danych do generatora w taki sam sposób można go otrzymać. Aby wysłać dane, trzeba mieć możliwość przypisania do nich, oraz w tym przypadku oświadczenie po prostu nie zadziała.


254
2018-06-25 06:11





yield jest po prostu jak return - zwraca to, co powiesz (jako generator). Różnica polega na tym, że następnym razem, gdy wywołasz generator, wykonywanie rozpocznie się od ostatniego połączenia do yield komunikat. W przeciwieństwie do powrotu, Ramka stosu nie jest czyszczona, gdy występuje wydajność, jednak sterowanie jest przekazywane z powrotem do osoby dzwoniącej, więc jej stan zostanie wznowiony przy następnym uruchomieniu funkcji.

W przypadku Twojego kodu funkcja get_child_candidates działa jak iterator, więc kiedy rozszerzasz listę, dodaje ona po jednym elemencie do nowej listy.

list.extend wywołuje iterator, dopóki nie zostanie wyczerpany. W przypadku wysłanego przez ciebie egzemplarza kodu, byłoby znacznie wyraźniej po prostu zwrócić krotkę i dołączyć ją do listy.


230
2017-10-23 22:24



Jest blisko, ale nie jest poprawne. Za każdym razem, gdy wywołujesz funkcję z instrukcją yield, zwraca ona zupełnie nowy obiekt generatora. Dopiero po wywołaniu tej metody generatora .next () wykonanie zostanie wznowione po ostatniej wydajności. - kurosch
Wygląda na to, że czegoś brakuje "wznowi działanie następnym razem". Tak być powinno "wznowi działanie następnym razem działa"? - Peter Mortensen


Jest jeszcze jedna rzecz do wymienienia: funkcja, której wydajność nie musi się kończyć. Napisałem kod w ten sposób:

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

Wtedy mogę użyć go w innym kodzie jak poniżej:

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

To naprawdę pomaga uprościć niektóre problemy i sprawia, że ​​niektóre rzeczy są łatwiejsze w obsłudze.


182
2017-10-24 08:44