Aby zrozumieć, co yield
robi, musisz zrozumieć, czym są generatory . I zanim zrozumiesz generatory, musisz zrozumieć iterowalne .
Iterables
Kiedy tworzysz listę, możesz odczytywać jej elementy jeden po drugim. Czytanie poszczególnych pozycji jeden po drugim nazywa się iteracją:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
jest iterowalny . Kiedy używasz rozumienia listy, 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ć na „ for... in...
”, jest iterowalne; lists
, strings
, Pliki ...
Te iteracje są przydatne, ponieważ możesz je odczytać tyle, ile chcesz, ale wszystkie wartości przechowujesz w pamięci i nie zawsze jest to potrzebne, gdy masz wiele wartości.
Generatory
Generatory są iteratorami, rodzajem iteracji, które można iterować tylko raz . Generatory nie przechowują wszystkich wartości w pamięci, generują je w locie :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Jest tak samo, z wyjątkiem tego, że użyłeś ()
zamiast []
. ALE nie możesz wykonać for i in mygenerator
drugiego razu, ponieważ generatory mogą być używane tylko raz: obliczają 0, następnie zapominają o tym i obliczają 1, i kończą obliczanie 4, jeden po drugim.
Wydajność
yield
jest słowem kluczowym, które jest używane podobnie return
, ale 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
Oto bezużyteczny przykład, ale jest przydatny, gdy wiesz, że twoja funkcja zwróci ogromny zestaw wartości, które będziesz musiał odczytać tylko raz.
Aby opanować yield
, musisz zrozumieć, że po wywołaniu funkcji kod napisany w treści funkcji nie jest uruchamiany. Funkcja zwraca tylko obiekt generatora, jest to nieco trudne :-)
Następnie kod będzie kontynuowany od miejsca, w którym został przerwany za każdym razem, gdy for
używa generatora.
Teraz najtrudniejsza część:
Przy pierwszym for
wywołaniu obiektu generatora utworzonego z twojej funkcji, uruchomi on kod w twojej funkcji od początku aż do trafienia yield
, a następnie zwróci pierwszą wartość pętli. Następnie każde kolejne wywołanie uruchomi kolejną iterację pętli zapisanej w funkcji i zwróci następną wartość. Będzie to trwało, dopóki generator nie zostanie uznany za pusty, co dzieje się, gdy funkcja działa bez uderzenia yield
. Może to być spowodowane tym, że pętla dobiegła końca lub dlatego, że nie spełniasz już warunku "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 the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
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 candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Ten kod zawiera kilka inteligentnych części:
Pętla iteruje się na liście, ale lista rozszerza się podczas iteracji :-) Jest to zwięzły sposób na przejrzenie wszystkich tych zagnieżdżonych danych, nawet jeśli jest to trochę niebezpieczne, ponieważ możesz skończyć z nieskończoną pętlą. W takim przypadku candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
wyczerp wszystkie wartości generatora, ale while
nadal tworzy nowe obiekty generatora, które będą generować inne wartości niż poprzednie, ponieważ nie są one stosowane w tym samym węźle.
extend()
Metoda jest lista metoda obiekt, który oczekuje iterable i dodaje swoje wartości do listy.
Zwykle podajemy do niej listę:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Ale w kodzie dostaje generator, co jest dobre, ponieważ:
- Nie musisz czytać wartości dwa razy.
- 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 argumentem metody jest lista, czy nie. Python oczekuje iterable, więc będzie działał z ciągami, listami, krotkami i generatorami! Nazywa się to pisaniem kaczek i jest jednym z powodów, dla których Python jest taki fajny. Ale to już inna historia, na inne pytanie ...
Możesz się tu zatrzymać lub poczytać, aby zobaczyć zaawansowane zastosowanie 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 przypadku Python 3 użyj print(corner_street_atm.__next__())
lubprint(next(corner_street_atm))
Może to być przydatne 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łeś zduplikować generator? Łańcuch dwóch generatorów? Czy grupować wartości na zagnieżdżonej liście za pomocą jednego wiersza? Map / Zip
bez tworzenia kolejnej listy?
Więc po prostu import itertools
.
Przykład? Zobaczmy możliwe rozkazy przylotów dla wyścigu 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 jest procesem polegającym na iteracjach (implementacja __iter__()
metody) i iteratorach (implementacja __next__()
metody). Iterabile to dowolne obiekty, z których można uzyskać iterator. Iteratory to obiekty, które pozwalają iterować na iterowalnych.
Więcej informacji na ten temat można znaleźć w tym artykule na temat działania for
pętli .
yield
ta odpowiedź nie jest tak magiczna. Kiedy wywołujesz funkcję, która zawierayield
instrukcję 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, aż dojdzie doyield
instrukcji, a następnie zatrzymuje się i dostarcza obiekt. Kiedy wyodrębniasz inny obiekt, Python wznawia się zaraz po nimyield
i kontynuuje, aż osiągnie innyyield
(często ten sam, ale jedna iteracja później). Trwa to do momentu, gdy funkcja przestanie działać, w tym momencie generator zostanie uznany za wyczerpany.()
zamiast[]
, a konkretnie tego, co()
jest (może wystąpić pomyłka z krotką).Skrót do zrozumienia
yield
Gdy zobaczysz funkcję z
yield
instrukcjami, zastosuj tę prostą sztuczkę, aby zrozumieć, co się stanie:result = []
na początku funkcji.yield expr
zresult.append(expr)
.return result
na dole funkcji.yield
oświadczeń! Przeczytaj i wymyśl kod.Ta sztuczka może dać ci wyobrażenie o logice funkcji, ale o tym, co się właściwie dzieje
yield
różni się znacznie od tego, co dzieje się w podejściu opartym na liście. W wielu przypadkach metoda wydajności będzie znacznie wydajniejsza i szybsza. W innych przypadkach ta sztuczka utknie w nieskończonej pętli, mimo że oryginalna funkcja działa dobrze. Czytaj dalej, aby dowiedzieć się więcej ...Nie myl swoich Iterabajtów, Iteratorów i Generatorów
Po pierwsze, protokół iteratora - podczas pisania
Python wykonuje następujące dwa kroki:
Pobiera iterator dla
mylist
:Wywołanie
iter(mylist)
-> zwraca obiekt znext()
metodą (lub__next__()
w Pythonie 3).[To jest krok, o którym większość ludzi zapomina ci powiedzieć]
Używa iteratora do zapętlania elementów:
Nadal wywołuj
next()
metodę na iteratorze zwróconą z kroku 1. Zwracana wartość znext()
jest przypisywana dox
ciała pętli i wykonywane. Jeśli wyjątekStopIteration
zostanie zgłoszony od wewnątrznext()
, oznacza to, że w iteratorze nie ma już więcej wartości i pętla zostaje 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
otherlist.extend(mylist)
(gdzieotherlist
jest lista Pythona).Tutaj
mylist
jest iterable ponieważ implementuje protokół iteratora. W klasie zdefiniowanej przez użytkownika można zaimplementować tę__iter__()
metodę, aby instancje klasy były iterowalne. Ta metoda powinna zwrócić iterator . Iterator to obiekt znext()
metodą. Możliwe jest zaimplementowanie obu__iter__()
inext()
na tej samej klasie i__iter__()
zwrotself
. 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.To jest protokół iteratora, wiele obiektów implementuje ten protokół:
__iter__()
.Zauważ, że
for
pętla nie wie, z jakim obiektem ma do czynienia - po prostu postępuje zgodnie z protokołem iteratora i chętnie otrzymuje element po elemencie, gdy wywołujenext()
. Wbudowane listy zwracają swoje elementy jeden po drugim, słowniki zwracają klucze jeden po drugim, pliki zwracają wiersze jeden po drugim itd. I generatory zwracają ... cóż, toyield
jest miejsce:Zamiast
yield
instrukcji, gdybyś miał trzyreturn
instrukcjef123()
tylko pierwsza byłaby wykonana, a funkcja by się zakończyła. Alef123()
to nie jest zwykła funkcja. Kiedyf123()
jest wywoływany, nie zwraca żadnej wartości z instrukcji return! Zwraca obiekt generatora. Ponadto funkcja tak naprawdę nie wychodzi - przechodzi w stan zawieszenia. Gdyfor
pętla próbuje zapętlić obiekt generatora, funkcja wznawia działanie ze stanu zawieszenia w następnym wierszu poyield
poprzednio zwróconym, wykonuje następny wiersz kodu, w tym przypadkuyield
instrukcję, i zwraca to jako następny pozycja. Dzieje się tak, dopóki funkcja nie wyjdzie, w którym momencie generator się podnosiStopIteration
, a pętla nie wychodzi.Tak więc obiekt generatora przypomina coś w rodzaju adaptera - na jednym końcu pokazuje protokół iteratora, ujawniając
__iter__()
inext()
metody utrzymywaniafor
szczęśliwej pętli. Z drugiej strony jednak uruchamia funkcję na tyle, aby uzyskać z niej następną wartość i przełącza ją z powrotem w tryb zawieszony.Dlaczego warto korzystać z generatorów?
Zwykle możesz pisać kod, który nie używa generatorów, ale implementuje tę samą logikę. Jedną z opcji jest użycie wspomnianej wcześniej „sztuczki” z listy tymczasowej. To nie zadziała we wszystkich przypadkach, na przykład jeśli masz nieskończone pętle lub może nieefektywnie wykorzystywać pamięć, gdy masz naprawdę długą listę. Drugim podejściem jest zaimplementowanie nowej klasy iterowalnej SomethingIter, która utrzymuje stan w elementach instancji i wykonuje kolejny logiczny krok w metodzie it
next()
(lub__next__()
w Pythonie 3). W zależności od logiki kod wewnątrznext()
metody może wyglądać na bardzo skomplikowany i podatny na błędy. Tutaj generatory zapewniają czyste i łatwe rozwiązanie.źródło
send
do generatora, który jest ogromną częścią sedna generatorów?otherlist.extend(mylist)
” -> To jest niepoprawne.extend()
modyfikuje listę w miejscu i nie zwraca iterowalności. Próba zapętlenia zakończyotherlist.extend(mylist)
się niepowodzeniem,TypeError
ponieważextend()
niejawnie powracaNone
i nie można zapętlićNone
.mylist
(nie włączonyotherlist
)otherlist.extend(mylist)
.Pomyśl o tym w ten sposób:
Iterator to tylko fantazyjnie brzmiący termin dla obiektu, który ma
next()
metodę. Tak więc funkcja wydajna kończy się mniej więcej tak:Orginalna wersja:
Jest to w zasadzie to, co interpreter Pythona robi z powyższym kodem:
Aby uzyskać więcej wglądu w to, co dzieje się za kulisami,
for
pętlę można przepisać na:Czy to ma więcej sensu, czy tylko bardziej dezorientuje? :)
Powinienem zauważyć, że jest to nadmierne uproszczenie w celach ilustracyjnych. :)
źródło
__getitem__
można zdefiniować zamiast__iter__
. Na przykładclass it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
iterator = some_function()
, zmiennaiterator
nie ma już funkcji o nazwienext()
, ale tylko__next__()
funkcję. Myślałem, że o tym wspomnę.for
napisana przez Ciebie implementacja pętli wywołuje__iter__
metodęiterator
instancji instancjiit
?Słowo
yield
kluczowe ogranicza się do dwóch prostych faktów:yield
słowo kluczowe w dowolnym miejscu funkcji, funkcja ta nie zwraca jużreturn
instrukcji. Zamiast tego natychmiast zwraca leniwy obiekt „listy oczekujących” zwany generatoremlist
lubset
lubrange
widok dykta, z wbudowanym protokołem do odwiedzania każdego elementu w określonej kolejności .W skrócie: generator jest leniwą, rosnącą listą , a
yield
instrukcje pozwalają na użycie notacji funkcji do zaprogramowania wartości listy, które generator powinien stopniowo wypluć.Przykład
Zdefiniujmy funkcję
makeRange
podobną do Pythonarange
. WywoływaniemakeRange(n)
ZWRACA GENERATOR:Aby zmusić generator do natychmiastowego zwrócenia oczekujących wartości, możesz przekazać go do
list()
(tak jak możesz to zrobić w dowolnej iteracji):Porównywanie przykładu z „po prostu zwracaniem listy”
Powyższy przykład można traktować jako tworzenie listy, do której dołączasz i którą zwracasz:
Jest jednak jedna zasadnicza różnica; patrz ostatnia sekcja.
Jak możesz korzystać z generatorów
Iterowalny jest ostatnią częścią zrozumienia listy, a wszystkie generatory są iterowalne, więc często używa się ich w ten sposób:
Aby lepiej poznać generatory, możesz pobawić się
itertools
modułem (pamiętaj, aby używaćchain.from_iterable
raczej niżchain
wtedy, gdy jest to uzasadnione). Na przykład możesz nawet użyć generatorów, aby zaimplementować nieskończenie długie leniwe listy, takie jakitertools.count()
. Możesz zaimplementować własnedef enumerate(iterable): zip(count(), iterable)
lub alternatywnie zrobić to za pomocąyield
słowa kluczowego w pętli while.Uwaga: generatory mogą być używane do wielu innych rzeczy, takich jak implementacja coroutines lub programowania niedeterministycznego lub innych eleganckich rzeczy. Jednak punkt widzenia „leniwych list”, który tu prezentuję, jest najczęstszym zastosowaniem, jaki można znaleźć.
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ę”.Wbudowana funkcja
next()
po prostu wywołuje funkcję obiektów.next()
, która jest częścią „protokołu iteracji” i znajduje się na wszystkich iteratorach. Możesz ręcznie użyćnext()
funkcji (i innych części protokołu iteracji), aby zaimplementować wymyślne rzeczy, zwykle kosztem czytelności, więc staraj się tego nie robić ...Drobne szczegóły
Zwykle większość ludzi nie przejmowałaby się następującymi wyróżnieniami i prawdopodobnie chciałaby przestać tutaj czytać.
W Python-speak iterowalnym jest każdy obiekt, który „rozumie pojęcie pętli for”, taki jak lista
[1,2,3]
, a iterator jest specyficzną instancją żądanej pętli for[1,2,3].__iter__()
. Generator jest dokładnie taki sam jak każdy iteratora, z wyjątkiem sposób został napisany (ze składnią funkcji).Gdy poprosisz o iterator z listy, tworzy on nowy iterator. Jednak gdy poprosisz o iterator od iteratora (co rzadko byś robił), po prostu daje ci kopię samego siebie.
Dlatego w mało prawdopodobnym przypadku, gdy nie robisz czegoś takiego ...
... pamiętaj więc, że generator jest iteratorem ; to jest jednorazowe użycie. Jeśli chcesz go ponownie użyć, zadzwoń
myRange(...)
ponownie. Jeśli musisz użyć wyniku dwa razy, przekonwertuj wynik na listę i zapisz go w zmiennejx = list(myRange(5))
. Osoby, które absolutnie trzeba sklonować generator (na przykład, którzy robią przerażająco hackish metaprogramowanie) można użyćitertools.tee
, jeśli jest to absolutnie konieczne, ponieważ copyable iterator Python PEP standardy propozycja została odroczona.źródło
Odpowiedź Konspekt / Podsumowanie
yield
, po wywołaniu, zwraca generator .yield from
.return
w generatorze.)Generatory:
yield
jest legalne tylko w definicji funkcji, a włączenieyield
do definicji funkcji powoduje, że zwraca generator.Pomysł na generatory pochodzi z innych języków (patrz przypis 1) z różnymi implementacjami. W Generatorach Pythona wykonanie kodu jest zawieszane w punkcie wydajności. Po wywołaniu generatora (metody omówiono 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 za pomocą dwóch następujących metod:__iter__
oraznext
(Python 2) lub__next__
(Python 3). Obie te metody sprawiają, że obiekt jest iteratorem, który można sprawdzić za pomocąIterator
abstrakcyjnej klasy bazowej zcollections
modułu.Generator jest podtypem iteratora:
Jeśli to konieczne, możemy wpisać:
Cechą tego jest
Iterator
to, że po wyczerpaniu nie można go ponownie użyć ani zresetować:Będziesz musiał zrobić kolejny, jeśli chcesz ponownie skorzystać z jego funkcjonalności (patrz przypis 2):
Można uzyskać dane programowo, na przykład:
Powyższy prosty generator jest również równoważny z poniższym - od wersji Python 3.3 (i niedostępnej w Python 2) możesz użyć
yield from
:Jednak
yield from
zezwala również na delegowanie do podrzędnych generatorów, co zostanie wyjaśnione w poniższej części dotyczącej delegowania kooperacyjnego z podkorupami.Coroutines:
yield
tworzy wyrażenie, które pozwala na przesłanie danych do generatora (patrz przypis 3)Oto przykład, zwróć uwagę na
received
zmienną, która będzie wskazywać na dane wysyłane do generatora:Po pierwsze, musimy stać w kolejce do generatora za pomocą funkcji wbudowanej,
next
. Wywoła odpowiednią metodęnext
lub__next__
metodę, w zależności od używanej wersji Pythona:A teraz możemy wysłać dane do generatora. ( Wysyłanie
None
jest takie samo jak dzwonienienext
.):Delegacja kooperacyjna w Sub-Coroutine z
yield from
Przypomnijmy, że
yield from
jest dostępny w Pythonie 3. To pozwala nam delegować coroutines do podprogramu:A teraz możemy przekazać funkcjonalność subgeneratorowi i może on być używany przez generator tak jak powyżej:
Możesz przeczytać więcej o dokładnej semantyce
yield from
w PEP 380.Inne metody: zamknij i rzuć
close
Metoda budziGeneratorExit
w punkcie wykonanie funkcja została zamrożona. Zostanie to również wywołane__del__
, abyś mógł umieścić dowolny kod czyszczenia w miejscu, w którym wykonujeszGeneratorExit
:Możesz także zgłosić wyjątek, który można obsłużyć w generatorze lub przekazać użytkownikowi:
Wniosek
Uważam, że omówiłem wszystkie aspekty następującego pytania:
Okazuje się, że
yield
dużo. Jestem pewien, że mógłbym dodać do tego jeszcze dokładniejsze przykłady. Jeśli chcesz więcej lub masz konstruktywną krytykę, daj mi znać, komentując poniżej.Dodatek:
Krytyka najpopularniejszej / zaakceptowanej odpowiedzi **
__iter__
metodę zwracającą iterator . Iterator zapewnia.next
(Python 2 lub.__next__
3 metody (Python), który jest niejawnie wywoływana przezfor
pętle aż podnosiStopIteration
, a gdy tak się stanie, to będzie nadal to robić.yield
..next
metodę, gdy zamiast powinien używać funkcji wbudowanego,next
. Byłaby to odpowiednia warstwa pośrednicząca, ponieważ jego kod nie działa w Pythonie 3.yield
w ogóle.yield
udostępnianych wraz z nową funkcjonalnościąyield from
w Pythonie 3. Górna / zaakceptowana odpowiedź jest bardzo niepełną odpowiedzią.Krytyka odpowiedzi sugerująca
yield
w wyrażeniu lub zrozumieniu generatora.Gramatyka obecnie pozwala na dowolne wyrażanie się w zrozumieniu listy.
Ponieważ wydajność jest wyrażeniem, niektórzy reklamują ją jako interesującą w użyciu w zrozumieniu lub wyrażeniu generatora - pomimo braku cytowania szczególnie dobrego przypadku użycia.
Główni programiści CPython dyskutują nad wycofaniem tego limitu . Oto odpowiedni post z listy mailingowej:
Ponadto istnieje nierozstrzygnięty problem (10544), który wydaje się wskazywać, że nigdy nie jest to dobry pomysł (PyPy, implementacja Pythona napisana w Pythonie, już wywołuje ostrzeżenia składniowe).
Podsumowując, dopóki programiści CPython nie powiedzą nam inaczej: Nie wpisuj
yield
wyrażenia ani zrozumienia generatora.return
Oświadczenie w generatorzeW Python 2 :
An
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żesz zwrócić wartości.W Pythonie 3 :
Przypisy
Języki CLU, Sather i Icon zostały przywołane w propozycji wprowadzenia koncepcji generatorów do Pythona. Ogólna idea jest taka, że funkcja może utrzymywać stan wewnętrzny i generować pośrednie punkty danych na żądanie użytkownika. Zapowiadało się, że będzie lepsza pod względem wydajności niż inne podejścia, w tym wątki Pythona , które nie są nawet dostępne w niektórych systemach.
Oznacza to na przykład, że
xrange
obiekty (range
w Pythonie 3) nie są obiektamiIterator
, nawet jeśli są iterowalne, ponieważ można je ponownie wykorzystać. Podobnie jak listy, ich__iter__
metody zwracają obiekty iteratora.yield
został pierwotnie wprowadzony jako instrukcja, co oznacza, że może pojawić się tylko na początku wiersza w bloku kodu. Terazyield
tworzy wyrażenie wydajności. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Ta zmiana została zaproponowana, aby umożliwić użytkownikowi wysyłanie danych do generatora dokładnie tak, jak można go otrzymać. Aby wysłać dane, trzeba umieć je do czegoś przypisać, a do tego oświadczenie po prostu nie zadziała.źródło
yield
jest jakreturn
- zwraca wszystko, co mu powiesz (jako generator). Różnica polega na tym, że przy następnym wywołaniu generatora wykonanie rozpoczyna się od ostatniego wywołaniayield
instrukcji. W przeciwieństwie do return, ramka stosu nie jest czyszczona, gdy pojawia się wydajność, jednak kontrola jest przenoszona z powrotem do programu wywołują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 kiedy rozszerzasz listę, dodaje jeden element na raz do nowej listy.list.extend
wywołuje iterator, aż zostanie wyczerpany. W przypadku opublikowanego przykładu kodu znacznie łatwiej byłoby po prostu zwrócić krotkę i dołączyć ją do listy.źródło
Jest jeszcze jedna rzecz, o której należy wspomnieć: funkcja, która daje wartość, nie musi tak naprawdę kończyć się. Napisałem taki kod:
Następnie mogę użyć go w innym kodzie:
To naprawdę pomaga uprościć niektóre problemy i ułatwia pewne rzeczy.
źródło
Dla tych, którzy preferują minimalny działający przykład, rozważ medytację podczas tej interaktywnej sesji Pythona:
źródło
TL; DR
Zamiast tego:
Zrób to:
Ilekroć zaczynasz budować listę od zera,
yield
zamiast tego każdy kawałek.To był mój pierwszy „aha” moment z plonem.
yield
to słodki sposób na powiedzenieTo samo zachowanie:
Różne zachowanie:
Wydajność jest jednoprzebiegowa : możesz iterować tylko raz. Gdy funkcja ma w sobie wydajność, nazywamy ją funkcją generatora . I zwraca iterator . Te warunki są odkrywcze. Tracimy wygodę kontenera, ale zyskujemy moc serii, która jest obliczana w razie potrzeby i dowolnie długa.
Wydajność jest leniwa , odkłada obliczenia. Funkcja z wydajnością wcale nie jest wykonywana, gdy ją wywołujesz. Zwraca obiekt iteratora, który pamięta, gdzie został przerwany. Za każdym razem, gdy wywołujesz
next()
iterator (dzieje się tak w pętli for), wykonanie jest o kilka cali do następnej wydajności.return
podnosi StopIteration i kończy serię (jest to naturalny koniec pętli for).Wydajność jest wszechstronna . Dane nie muszą być przechowywane razem, mogą być udostępniane pojedynczo. Może być nieskończony.
Jeśli potrzebujesz wielu przejść, a seria nie jest zbyt długa, po prostu ją wywołaj
list()
:Świetny wybór tego słowa,
yield
ponieważ obowiązują oba znaczenia :... podaj kolejne dane z serii.
... porzuć wykonywanie procesora, dopóki iterator nie posunie się naprzód.
źródło
Wydajność daje ci generator.
Jak widać, w pierwszym przypadku
foo
cała lista jest jednocześnie 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 zjadacz pamięci, ale także kosztuje dużo czasu w momencie wywołania tej funkcji.W drugim przypadku
bar
po prostu daje ci generator. Generator jest iterowalny - co oznacza, że można go używać wfor
pętli itp., Ale do każdej wartości można uzyskać tylko raz. Wszystkie wartości nie są jednocześnie zapisywane w pamięci; obiekt generatora „zapamiętuje” miejsce, w którym znajdował się w pętli przy ostatnim wywołaniu - w ten sposób, jeśli używasz iterowalnego (powiedzmy) do policzenia do 50 miliardów, nie musisz liczyć do 50 miliardów wszystkich od razu i zapisz 50 miliardów liczb do przeliczenia.Ponownie, jest to dość wymyślony przykład, prawdopodobnie użyłbyś narzędzi itertool, jeśli naprawdę chciałbyś liczyć do 50 miliardów. :)
Jest to najprostszy przypadek użycia generatorów. Jak już powiedziałeś, można go użyć do napisania wydajnych permutacji, wykorzystując fedrunek do wypychania rzeczy przez stos wywołań, zamiast używać jakiejś zmiennej stosu. Generatory mogą być również używane do specjalistycznego przechodzenia przez drzewa i wszelkiego rodzaju innych rzeczy.
źródło
range
również zwraca generator zamiast listy, więc zobaczysz również podobny pomysł, z tym wyjątkiem, że__repr__
/__str__
są zastępowane, aby pokazać ładniejszy wynik, w tym przypadkurange(1, 10, 2)
.Zwraca generator. Nie jestem szczególnie zaznajomiony z Pythonem, ale uważam, że jest to ten sam rodzaj bloków iteratora C #, jeśli znasz je.
Kluczową ideą jest to, że kompilator / interpreter / cokolwiek robi pewne sztuczki, aby w odniesieniu do wywołującego mogli oni dalej wywoływać next () i nadal zwracali wartości - tak jakby metoda generatora została wstrzymana . Teraz oczywiście nie można tak naprawdę „wstrzymać” metody, więc kompilator buduje maszynę stanów, aby zapamiętać, gdzie się aktualnie znajdujesz i jak wyglądają zmienne lokalne itp. Jest to o wiele łatwiejsze niż samodzielne pisanie iteratora.
źródło
Jest jeden rodzaj odpowiedzi, która nie wydaje mi się, że została jeszcze udzielona, spośród wielu wspaniałych odpowiedzi opisujących sposób korzystania z generatorów. Oto odpowiedź teorii języka programowania:
yield
Oświadczenie w Pythonie zwraca generator. Generator w Pythonie jest funkcją, która zwraca kontynuacje (a konkretnie rodzaj coroutine, ale kontynuacje reprezentują bardziej ogólny mechanizm rozumienia, co się dzieje).Kontynuacja teorii języków programowania jest znacznie bardziej fundamentalnym rodzajem obliczeń, ale nie jest często używana, ponieważ są one bardzo trudne do uzasadnienia, a także bardzo trudne do wdrożenia. Jednak idea kontynuacji jest prosta: jest to stan obliczeń, który jeszcze się nie zakończył. W tym stanie są zapisywane bieżące wartości zmiennych, operacje, które jeszcze nie zostały wykonane itd. Następnie w pewnym momencie w programie można wywołać kontynuację, tak aby zmienne programu zostały zresetowane do tego stanu i wykonane zostały zapisane operacje.
Kontynuacje, w tej bardziej ogólnej formie, można realizować na dwa sposoby. W ten
call/cc
sposób stos programu jest dosłownie zapisywany, a następnie przy wywołaniu kontynuacji stos jest przywracany.W stylu kontynuacji przekazywania (CPS) kontynuacje są po prostu normalnymi funkcjami (tylko w językach, w których funkcje są pierwszej klasy), którymi programista wyraźnie zarządza i przekazuje podprogramy. W tym stylu stan programu jest reprezentowany przez zamknięcia (i zmienne, które się w nich zakodowały), a nie zmienne, które znajdują się gdzieś na stosie. Funkcje zarządzające przepływem sterowania przyjmują kontynuację jako argumenty (w niektórych odmianach CPS funkcje mogą przyjmować wiele kontynuacji) i manipulują 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:
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 zapisania), a następnie przekazuje tę kontynuację (tj. Jako pierwszy- zamknięcie klasy) innemu operatorowi, który wykonuje więcej przetwarzania, a następnie wywołuje je, jeśli to konieczne. (Często używam tego wzorca projektowego w rzeczywistym programowaniu GUI, ponieważ oszczędza mi on wiersze kodu lub, co ważniejsze, zarządza przepływem sterowania po uruchomieniu zdarzeń GUI.)
Pozostała część tego postu, bez utraty ogólności, będzie konceptualizować kontynuacje jako CPS, ponieważ jest o wiele łatwiejsze do zrozumienia i przeczytania.
Porozmawiajmy teraz o generatorach w Pythonie. Generatory są szczególnym podtypem kontynuacji. Podczas gdy kontynuacje są w stanie ogólnie zapisać stan obliczeń (tj. Stos wywołań programu), generatory są w stanie zapisać stan iteracji tylko przez iterator . Chociaż ta definicja jest nieco myląca dla niektórych przypadków użycia generatorów. Na przykład:
Jest to wyraźnie uzasadniona iteracja, której zachowanie jest dobrze określone - za każdym razem, gdy generator iteruje nad nią, zwraca 4 (i robi to na zawsze). Ale prawdopodobnie nie jest to prototypowy typ iterowalności, który przychodzi na myśl, gdy myślimy 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 iteracji.Powtórzmy: 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, dużo łatwiejsze. Są one łatwiejsze do zaimplementowania przez projektanta języka i są łatwiejsze dla programisty (jeśli masz trochę czasu na nagranie, spróbuj przeczytać i zrozumieć tę stronę o kontynuacjach i wywołaniu / cc ).
Ale można łatwo wdrożyć (i konceptualizować) generatory jako prosty, konkretny przypadek stylu przekazywania kontynuacji:
Ilekroć
yield
jest wywoływane, mówi funkcji, aby zwróciła kontynuację. Gdy funkcja zostanie ponownie wywołana, zaczyna się od momentu jej zakończenia. Tak więc w pseudo-pseudokodzie (tj. Nie pseudokodzie, ale nie kodzie)next
metoda generatora jest zasadniczo następująca:gdzie
yield
słowem kluczowym jest faktycznie cukier składniowy dla prawdziwej funkcji generatora, w zasadzie coś takiego:Pamiętaj, że jest to tylko pseudokod, a faktyczna 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 do implementacji obiektów generatora bez użycia
yield
słowa kluczowego.źródło
Oto przykład w prostym języku. Zapewnię zgodność między wysokopoziomowymi ludzkimi koncepcjami a niskopoziomowymi koncepcjami Python.
Chcę operować na sekwencji liczb, ale nie chcę zawracać sobie głowy tworzeniem tej sekwencji, chcę skupić się tylko na operacji, którą chcę wykonać. Więc wykonuję następujące czynności:
Ten krok odpowiada
def
wprowadzeniu funkcji generatora, tj. Funkcji zawierającej ayield
.Ten krok odpowiada wywołaniu funkcji generatora, która zwraca obiekt generatora. Pamiętaj, że nie podałeś mi jeszcze żadnych liczb; po prostu chwytasz papier i ołówek.
Ten krok odpowiada wywołaniu
.next()
obiektu generatora.Ten krok odpowiada obiektowi generatora kończącemu zadanie i
StopIteration
zgłoszeniu wyjątku Funkcja generatora nie musi zgłaszać wyjątku. Jest podnoszony automatycznie, gdy funkcja kończy się lub wydaje areturn
.Tak właśnie działa generator (funkcja zawierająca a
yield
); zaczyna się wykonywać, zatrzymuje się za każdym razem, gdy wykonuje ayield
, a po zapytaniu o.next()
wartość kontynuuje od momentu, w którym był ostatni. Idealnie pasuje do projektu z iteracyjnym protokołem Pythona, który opisuje, jak sekwencyjnie żądać wartości.Najbardziej znanym użytkownikiem protokołu iteratora jest
for
polecenie w Pythonie. Tak więc, kiedykolwiek zrobisz:nie ma znaczenia, czy
sequence
jest to lista, ciąg, słownik lub obiekt generatora, jak opisano powyżej; wynik jest taki sam: odczytujesz elementy z sekwencji jeden po drugim.Zauważ, że
def
wprowadzenie funkcji zawierającejyield
słowo kluczowe nie jest jedynym sposobem na utworzenie generatora; to tylko najprostszy sposób na jego utworzenie.Aby uzyskać dokładniejsze informacje, przeczytaj o typach iteratorów , instrukcji fedr i generatorach w dokumentacji Pythona.
źródło
Wiele odpowiedzi pokazuje, dlaczego warto użyć
yield
generatora, ale jest więcej zastosowańyield
. Bardzo łatwo jest stworzyć coroutine, która umożliwia przekazywanie informacji między dwoma blokami kodu. Nie powtórzę żadnego z dobrych przykładów, które zostały już podane na tematyield
tworzenia generatora.Aby zrozumieć, co
yield
robi a w poniższym kodzie, możesz użyć palca, aby prześledzić cykl przez dowolny kod, który mayield
. Za każdym razem, gdy palec dotykayield
, musisz poczekać na wprowadzenie znakunext
lubsend
. Kiedy anext
jest wywoływane, prześledzisz kod, dopóki nie naciśnieszyield
… kod po prawej stronieyield
jest analizowany i zwracany do osoby dzwoniącej… wtedy czekasz. Ponext
ponownym wywołaniu wykonujesz kolejną pętlę za pomocą kodu. Zauważysz jednak, że w coroutineyield
można również używać zsend
…, który wyśle wartość od osoby wywołującej do funkcji ustępowania. Jeślisend
podano a, toyield
odbiera wysłaną wartość i wypluwa ją po lewej stronie… następnie śledzenie kodu postępuje, aż doyield
ponownego trafienia (zwracanie wartości na końcu, tak jakbynext
została wywołana).Na przykład:
źródło
Istnieje inne
yield
zastosowanie i znaczenie (od Python 3.3):Z PEP 380 - Składnia delegowania do subgeneratora :
Ponadto ten wprowadzi (od Pythona 3.5):
aby uniknąć pomylenia coroutines ze zwykłym generatorem (dziś
yield
jest używany w obu).źródło
Wszystkie świetne odpowiedzi, choć trochę trudne dla początkujących.
Zakładam, że nauczyłeś się tego
return
oświadczenia.Analogicznie,
return
iyield
są bliźniakami.return
oznacza „zwróć i zatrzymaj”, podczas gdy „dochód” oznacza „zwróć, ale kontynuuj”Uruchom:
Widzisz, dostajesz tylko jeden numer, a nie ich listę.
return
nigdy nie pozwala ci zwyciężyć, po prostu wdraża raz i wychodzi.Wymienić
return
zyield
:Teraz wygrywasz, aby uzyskać wszystkie liczby.
W porównaniu z tym,
return
który uruchamia się raz, a zatrzymuje,yield
uruchamia zaplanowane czasy. Możesz interpretowaćreturn
jakoreturn one of them
iyield
jakoreturn all of them
. To się nazywaiterable
.To jest sedno
yield
.Różnica między
return
danymiyield
wyjściowymi listy a danymi wynikowymi obiektu jest następująca:Zawsze otrzymasz [0, 1, 2] z obiektu listy, ale tylko
yield
raz możesz je odzyskać z „obiektu wynikowego”. Tak więc ma nowygenerator
obiekt nazwy wyświetlany wOut[11]: <generator object num_list at 0x10327c990>
.Podsumowując, jako metafora, by to zrozumieć:
return
iyield
są bliźniakamilist
igenerator
są bliźniakamiźródło
yield
. Myślę, że to ważne i należy to wyrazić.Oto kilka przykładów Pythona, jak w rzeczywistości zaimplementować generatory, tak jakby Python nie dostarczył dla nich cukru składniowego:
Jako generator Pythona:
Używanie zamknięć leksykalnych zamiast generatorów
Używanie zamknięć obiektów zamiast generatorów (ponieważ ClosuresAndObjectsAreEquivalent )
źródło
Zamierzałem zamieścić „przeczytaj stronę 19„ Python: Essential Reference ”Beazleya, aby uzyskać szybki opis generatorów”, ale wiele innych opublikowało już dobre opisy.
Zauważ też, że
yield
można go używać w koronach jako podwójnego ich zastosowania w funkcjach generatora. Chociaż nie jest to takie samo zastosowanie jak fragment kodu,(yield)
może być użyte jako wyrażenie w funkcji. Gdy program wywołujący wysyła wartość do metody przy użyciusend()
metody, wówczas coroutine będzie wykonywana do momentu(yield)
napotkania następnej instrukcji.Generatory i korporacje to świetny sposób na konfigurację aplikacji typu przepływ danych. Pomyślałem, że warto wiedzieć o innym zastosowaniu tego
yield
wyrażenia w funkcjach.źródło
Z punktu widzenia programowania, iteratory są implementowane jako thunks .
Aby zaimplementować iteratory, generatory i pule wątków w celu równoczesnego wykonywania itp. Jako „thunks” (zwane również funkcjami anonimowymi), wykorzystuje się wiadomości wysłane do obiektu zamknięcia, który ma program rozsyłający, a program rozsyłający 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 pomocnika.
Oto demonstracja wykorzystująca strukturę R6RS, ale semantyka jest absolutnie identyczna jak w Pythonie. Jest to ten sam model obliczeń i do przepisania go w Pythonie wymagana jest tylko zmiana składni.
źródło
Oto prosty przykład:
Wynik:
Nie jestem programistą Python, ale wydaje mi się, że
yield
utrzymuje pozycję przepływu programu, a następna pętla rozpoczyna się od pozycji „fed”. Wygląda na to, że czeka w tej pozycji, a tuż przed tym zwraca wartość na zewnątrz, a następnym razem będzie działać.To wydaje się być interesującą i miłą umiejętnością: D
źródło
Oto mentalny obraz tego, co
yield
robi.Lubię myśleć o wątku jako posiadającym stos (nawet jeśli nie jest zaimplementowany w ten sposób).
Kiedy wywoływana jest normalna funkcja, umieszcza zmienne lokalne na stosie, wykonuje pewne obliczenia, a następnie usuwa stos i zwraca. Wartości lokalnych zmiennych nigdy nie są widziane.
Dzięki
yield
funkcji, gdy kod zaczyna działać (tj. Po wywołaniu funkcji, zwracając obiekt generatora, któregonext()
metoda jest następnie wywoływana), podobnie umieszcza zmienne lokalne na stosie i przez chwilę oblicza. Ale kiedy trafi nayield
instrukcję, przed wyczyszczeniem części stosu i zwróceniem, pobiera migawkę swoich zmiennych lokalnych i przechowuje je w obiekcie generatora. Zapisuje również miejsce, w którym aktualnie jest ustawiony, w swoim kodzie (tj. Konkretnayield
instrukcja).Jest to więc rodzaj zamrożonej funkcji, na której opiera się generator.
Kiedy
next()
jest wywoływany później, pobiera rzeczy z funkcji na stos i ponownie go animuje. Funkcja kontynuuje obliczanie od momentu jej przerwania, nieświadomy faktu, że właśnie spędził wieczność w chłodni.Porównaj następujące przykłady:
Kiedy wywołujemy drugą funkcję, zachowuje się ona zupełnie inaczej niż pierwsza.
yield
Oświadczenie może być nieosiągalny, ale jeśli jest obecny wszędzie, to zmienia charakter z czym mamy do czynienia.Wywołanie
yielderFunction()
nie uruchamia kodu, ale generuje generator z kodu. (Być może dobrym pomysłem jest nazywanie takich rzeczyyielder
prefiksem dla czytelności).gi_code
Igi_frame
pola są przechowywane gdzie stanie zamrożonym. Badając jedir(..)
, możemy potwierdzić, że nasz powyższy model mentalny jest wiarygodny.źródło
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 linia po linii w sieci można użyć tejyield
funkcji w następujący sposób:Możesz użyć go w swoim kodzie w następujący sposób:
Wykonanie Kontrola przeniesienia gotcha
Kontrola wykonania zostanie przeniesiona z getNextLines () do
for
pętli, gdy wykonywana jest wydajność. Dlatego za każdym razem, gdy wywoływana jest metoda getNextLines (), wykonywanie rozpoczyna się od momentu, w którym zostało ostatnio wstrzymane.Krótko mówiąc, funkcja z następującym kodem
wydrukuje
źródło
Łatwy przykład, aby zrozumieć, co to jest:
yield
Dane wyjściowe to:
źródło
print(i, end=' ')
? W przeciwnym razie uważam, że domyślne zachowanie spowodowałoby umieszczenie każdej liczby w nowym wierszu(Moja poniższa odpowiedź mówi tylko z perspektywy korzystania z generatora Python, a nie z podstawowej implementacji mechanizmu generatora , który wymaga pewnych sztuczek w stosie i manipulacji stosem.)
Kiedy
yield
jest używana zamiastreturn
w funkcji python, funkcja ta jest przekształcana w coś specjalnego o nazwiegenerator function
. Ta funkcja zwróci obiektgenerator
typu. Słowoyield
kluczowe jest flagą informującą kompilator Pythona, aby specjalnie traktował taką funkcję. Normalne funkcje zakończą się, gdy jakaś wartość zostanie z niej zwrócona. Ale za pomocą kompilatora można uznać, że funkcja generatora może zostać wznowiona. Oznacza to, że kontekst wykonania zostanie przywrócony, a wykonanie będzie kontynuowane od ostatniego uruchomienia. Aż do jawnego wywołania return, który zgłosiStopIteration
wyjątek (który jest również częścią protokołu iteratora) lub dojdzie do końca funkcji. Znalazłem wiele referencji nagenerator
ten temat jedenzfunctional programming perspective
najbardziej strawnego.(Teraz chcę porozmawiać o uzasadnieniu
generator
iiterator
moim własnym zrozumieniu. Mam nadzieję, że pomoże ci to zrozumieć istotną motywację iteratora i generatora. Taka koncepcja pojawia się również w innych językach, takich jak C #).Jak rozumiem, kiedy chcemy przetworzyć wiązkę danych, zwykle najpierw przechowujemy dane gdzieś, a następnie przetwarzamy je jeden po drugim. Ale to naiwne podejście jest problematyczne. Jeśli ilość danych jest ogromna, wcześniejsze przechowywanie ich jako całości jest drogie. Zamiast więc przechowywać dane
data
bezpośrednio, dlaczego nie przechowywać jakiegoś rodzajumetadata
pośrednio, tjthe logic how the data is computed
.Istnieją 2 podejścia do zawijania takich metadanych.
as a class
. Jest to tak zwany,iterator
który implementuje protokół iteratora (tj. Metody__next__()
i__iter__()
). Jest to również często spotykany wzorzec projektowy iteratora .as a function
. To jest tak zwanegenerator function
. Ale pod maską zwróconygenerator object
wciążIS-A
iterator, ponieważ implementuje również protokół iteratora.Tak czy inaczej, tworzony jest iterator, tzn. Jakiś obiekt, który może dać ci dane, których potrzebujesz. Podejście OO może być nieco skomplikowane. W każdym razie, który z nich należy do ciebie, zależy od ciebie.
źródło
Podsumowując,
yield
instrukcja przekształca twoją funkcję w fabrykę, która wytwarza specjalny obiekt o nazwiegenerator
owinięty wokół ciała twojej oryginalnej funkcji. Kiedygenerator
iteruje się, wykonuje twoją funkcję, dopóki nie osiągnie następnego,yield
a następnie zawiesza wykonanie i ocenia na przekazaną wartośćyield
. Powtarza ten proces przy każdej iteracji, dopóki ścieżka wykonania nie opuści funkcji. Na przykład,po prostu wyjścia
Moc pochodzi z zastosowania generatora z pętlą, która oblicza sekwencję, generator wykonuje pętlę zatrzymując się za każdym razem, aby „wydać” kolejny wynik obliczeń, w ten sposób oblicza listę w locie, korzyścią jest pamięć zapisane dla szczególnie dużych obliczeń
Załóżmy, że chcesz utworzyć własną
range
funkcję, która generuje iterowalny zakres liczb, możesz to zrobić w ten sposób,i użyj tego w ten sposób;
Ale to jest nieefektywne, ponieważ
Na szczęście Guido i jego zespół byli wystarczająco hojni, aby opracować generatory, abyśmy mogli to zrobić;
Teraz przy każdej iteracji funkcja na wywoływanym generatorze
next()
wykonuje funkcję, dopóki nie osiągnie instrukcji „wydajność”, w której zatrzymuje się i „daje” wartość lub osiąga koniec funkcji. W tym przypadku przy pierwszym wywołaniunext()
wykonuje się do instrukcji fed i wydaj 'n', przy następnym wywołaniu wykona instrukcję przyrostu, wróci do „while”, oceni ją, a jeśli to prawda, zatrzyma się i ponownie wydaj 'n', będzie to kontynuowało do momentu, aż warunek while zwróci wartość false, a generator przejdzie do końca funkcji.źródło
Wydajność jest przedmiotem
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
stanowi barierę .Oznacza to, że uruchomi kod w twojej funkcji od początku, aż do momentu trafienia
yield
. Następnie zwróci pierwszą wartość pętli.Następnie każde kolejne wywołanie uruchomi pętlę, którą napisałeś w funkcji jeszcze raz, zwracając następną wartość, dopóki nie będzie żadnej wartości do zwrócenia.
źródło
Wiele osób używa
return
zamiastyield
, ale w niektórych przypadkachyield
może być bardziej efektywne i łatwiejsze w użyciu.Oto przykład, który
yield
jest zdecydowanie najlepszy dla:Obie funkcje robią to samo, ale
yield
używają trzech linii zamiast pięciu i mają o jedną zmienną mniej do zmartwienia.Jak widać, obie funkcje robią to samo. Jedyna różnica polega na
return_dates()
podaniu listy iyield_dates()
generatora.Przykładem może być czytanie pliku linia po linii lub jeśli chcesz po prostu stworzyć generator.
źródło
yield
jest jak element zwrotny dla funkcji. Różnica polega na tym, żeyield
element zamienia funkcję w generator. Generator zachowuje się jak funkcja, dopóki coś nie zostanie „wydane”. Generator zatrzymuje się, dopóki nie zostanie ponownie wywołany, i kontynuuje od dokładnie tego samego punktu, w którym się uruchomił. Możesz uzyskać sekwencję wszystkich „uzyskanych” wartości w jednym, wywołująclist(generator())
.źródło
Słowo
yield
kluczowe po prostu zbiera zwracane wyniki. Myśleć oyield
niczymreturn +=
źródło
Oto proste
yield
podejście do obliczenia serii Fibonacciego, wyjaśnione:Gdy wpiszesz to do REPL, a następnie spróbujesz go nazwać, otrzymasz tajemniczy rezultat:
Jest tak, ponieważ obecność
yield
sygnalizowanego Pythonowi, że chcesz utworzyć generator , to znaczy obiekt, który generuje wartości na żądanie.Jak więc generować te wartości? Można to zrobić albo bezpośrednio, używając wbudowanej funkcji
next
, albo pośrednio, przekazując ją do konstrukcji, która zużywa wartości.Korzystając z wbudowanej
next()
funkcji, bezpośrednio wywołujesz.next
/__next__
, zmuszając generator do wygenerowania wartości:Pośrednio, jeśli dostarczysz
fib
dofor
pętli,list
inicjalizatora,tuple
inicjatora lub czegokolwiek innego, co oczekuje, że obiekt generuje / generuje wartości, „zużyjesz” generator, dopóki nie będzie mógł wygenerować więcej wartości (i zwróci) :Podobnie z
tuple
inicjatorem:Generator różni się od funkcji tym, że jest leniwy. Dokonuje tego, utrzymując swój stan lokalny i umożliwiając wznowienie, kiedy tylko zajdzie taka potrzeba.
Gdy wywołujesz
fib
go po raz pierwszy :Python kompiluje funkcję, napotyka
yield
słowo kluczowe i po prostu zwraca obiekt generatora. Nie wydaje się to zbyt pomocne.Kiedy następnie zażądasz, generuje pierwszą wartość, bezpośrednio lub pośrednio, wykonuje wszystkie znalezione instrukcje, dopóki nie napotka znaku a
yield
, a następnie zwraca wartość, którą podałeśyield
i zatrzymuje się. Na przykład, który lepiej to pokazuje, użyjmy niektórychprint
wywołań (zamieńprint "text"
na if w Python 2):Teraz wprowadź REPL:
masz teraz obiekt generatora, który czeka na polecenie wygenerowania wartości. Użyj
next
i zobacz, co zostanie wydrukowane:Niecytowane wyniki są wydrukowane. Podany wynik jest zwracany
yield
. Zadzwońnext
ponownie teraz:Generator pamięta, że został zatrzymany
yield value
i wznawia stamtąd. Następna wiadomość jest drukowana i wyszukiwanieyield
instrukcji, która ma zostać wstrzymana, jest wykonywane ponownie (z powoduwhile
pętli).źródło