Ciężko mi się owija mózg wokół PEP 380 .
- Jakie są sytuacje, w których przydatne jest „uzyskiwanie z”?
- Jaki jest klasyczny przypadek użycia?
- Dlaczego porównuje się go z mikrowątkami?
[ aktualizacja ]
Teraz rozumiem przyczynę moich trudności. Używałem generatorów, ale tak naprawdę nigdy nie używałem koronek (wprowadzonych przez PEP-342 ). Pomimo pewnych podobieństw, generatory i korporacje to w zasadzie dwie różne koncepcje. Zrozumienie coroutines (nie tylko generatorów) jest kluczem do zrozumienia nowej składni.
Korpusy IMHO są najbardziej niejasną funkcją Pythona , większość książek sprawia, że wygląda bezużytecznie i nieciekawie.
Dzięki za świetne odpowiedzi, ale specjalne podziękowania dla agf i jego komentarza do prezentacji Davida Beazleya . David kołysze.
Odpowiedzi:
Najpierw usuńmy jedno. Wyjaśnienie, które
yield from g
jest równoważne zfor v in g: yield v
tym, nie zaczyna nawet oddawać sprawiedliwości temu, o co w tymyield from
wszystkim chodzi. Ponieważ, spójrzmy prawdzie w oczy, jeśli wszystkoyield from
zrobi, to rozwinięciefor
pętli, nie gwarantuje to dodaniayield from
do języka i wyklucza wprowadzenie całej gamy nowych funkcji w Pythonie 2.x.Co
yield from
to jest ustanawia przejrzyste dwukierunkowe połączenie między dzwoniącym a pod-generatorem :Połączenie jest „przezroczyste” w tym sensie, że propaguje również wszystko poprawnie, nie tylko generowane elementy (np. Propagowane są wyjątki).
Połączenie jest „dwukierunkowe” w tym sensie, że dane mogą być wysyłane zarówno z generatora, jak i do niego.
( Gdybyśmy mówili o TCP,
yield from g
może to oznaczać „teraz tymczasowo odłącz gniazdo mojego klienta i podłącz je ponownie do tego drugiego gniazda serwera” ).BTW, jeśli nie jesteś pewien, co w ogóle oznacza wysyłanie danych do generatora , musisz porzucić wszystko i najpierw przeczytać o coroutines - są one bardzo przydatne (w przeciwieństwie do podprogramów ), ale niestety mniej znane w Pythonie. Ciekawy kurs Dave'a Beazleya na coroutines to doskonały początek. Przeczytaj slajdy 24-33, aby uzyskać szybki podkład.
Odczyt danych z generatora przy użyciu wydajności z
Zamiast ręcznie iterować
reader()
, możemy toyield from
zrobić.To działa i wyeliminowaliśmy jeden wiersz kodu. I prawdopodobnie cel jest nieco jaśniejszy (lub nie). Ale nic nie zmienia życia.
Przesyłanie danych do generatora (coroutine) z wykorzystaniem wydajności z - Część 1
Zróbmy teraz coś ciekawszego. Stwórzmy nazwaną coroutine,
writer
która akceptuje wysyłane do niej dane i zapisuje w gnieździe, fd itp.Teraz pytanie brzmi: w jaki sposób funkcja opakowania powinna obsługiwać wysyłanie danych do programu piszącego, aby wszelkie dane wysyłane do opakowania były w sposób przezroczysty przesyłane do
writer()
?Opakowanie musi zaakceptować dane, które są do niego wysyłane (oczywiście) i powinno również obsłużyć
StopIteration
moment wyczerpania pętli for. Najwyraźniej samo robieniefor x in coro: yield x
tego nie da. Oto wersja, która działa.Lub możemy to zrobić.
Oszczędza to 6 linii kodu, czyni go znacznie bardziej czytelnym i po prostu działa. Magia!
Przesyłanie danych do generatora generuje z - Część 2 - Obsługa wyjątków
Zróbmy to bardziej skomplikowanym. Co jeśli nasz pisarz musi poradzić sobie z wyjątkami? Powiedzmy, że
writer
uchwytySpamException
a drukuje,***
jeśli je napotka.Co jeśli się nie zmienimy
writer_wrapper
? Czy to działa? SpróbujmyNie działa, bo
x = (yield)
podnosi wyjątek i wszystko się kończy. Sprawmy, by działało, ale ręcznie obsługujemy wyjątki i wysyłamy je lub wrzucamy do pod-generatora (writer
)To działa.
Ale tak też się dzieje!
W
yield from
przejrzysty uchwyty wysyłania wartości lub rzucanie wartości do sub-generator.Nie dotyczy to jednak wszystkich przypadków narożnych. Co się stanie, jeśli zewnętrzny generator zostanie zamknięty? Co z przypadkiem, gdy pod-generator zwraca wartość (tak, w Pythonie 3.3+, generatory mogą zwracać wartości), w jaki sposób należy propagować zwracaną wartość? To, że w
yield from
przejrzysty sposób obsługuje wszystkie skrzynki narożne, jest naprawdę imponujące .yield from
po prostu magicznie działa i obsługuje wszystkie te przypadki.Osobiście uważam, że
yield from
jest to zły wybór słów kluczowych, ponieważ nie uwidacznia to dwukierunkowej natury. Zaproponowano inne słowa kluczowe (jak,delegate
ale zostały odrzucone, ponieważ dodanie nowego słowa kluczowego do języka jest znacznie trudniejsze niż połączenie istniejących.Podsumowując, najlepiej jest traktować to
yield from
jako połączenietransparent two way channel
między dzwoniącym a sub-generatorem.Bibliografia:
źródło
except StopIteration: pass
WEWNĘTRZNEJwhile True:
pętli nie jest dokładnym odwzorowaniemyield from coro
- która nie jest nieskończoną pętlą i pocoro
wyczerpaniu (tj. podnosi StopIteration),writer_wrapper
wykona następną instrukcję. Po ostatnimStopIteration
writer
zawartyfor _ in range(4)
zamiastwhile True
, to po wydrukowaniu>> 3
TAKŻE również automatycznie podniesie,StopIteration
a to będzie automatycznie obsługiwane przez,yield from
a następniewriter_wrapper
automatycznie podniesie swoje własne,StopIteration
a ponieważwrap.send(i)
nie jest wtry
bloku, w rzeczywistości zostanie podniesione w tym momencie ( tzn. traceback zgłasza tylko linięwrap.send(i)
, a nie nic z wnętrza generatora)Każda sytuacja, w której masz taką pętlę:
Jak opisuje PEP, jest to dość naiwna próba użycia subgeneratora, brakuje w niej kilku aspektów, zwłaszcza poprawnej obsługi mechanizmów
.throw()
/.send()
/.close()
wprowadzonych przez PEP 342 . Aby zrobić to poprawnie, potrzebny jest raczej skomplikowany kod.Rozważ, że chcesz wyodrębnić informacje ze struktury danych rekurencyjnych. Powiedzmy, że chcemy uzyskać wszystkie węzły liści w drzewie:
Jeszcze ważniejsze jest to, że do tego czasu
yield from
nie było prostej metody refaktoryzacji kodu generatora. Załóżmy, że masz (bezsensowny) generator taki jak ten:Teraz decydujesz się rozdzielić te pętle na osobne generatory. Bez
yield from
tego jest to brzydkie, do tego stopnia, że pomyślisz dwa razy, czy naprawdę chcesz to zrobić. Dziękiyield from
naprawdę miło jest spojrzeć na:Myślę, że w tym rozdziale PEP jest mowa o tym, że każdy generator ma swój własny izolowany kontekst wykonania. Wraz z faktem, że wykonywanie jest przełączane między iteratorem generatora a wywołującym za pomocą
yield
i__next__()
, odpowiednio, jest to podobne do wątków, w których system operacyjny od czasu do czasu przełącza wykonujący wątek, wraz z kontekstem wykonania (stos, rejestry, ...)Efekt tego jest również porównywalny: zarówno generator-iterator, jak i program wywołujący postępują w stanie wykonania w tym samym czasie, ich wykonania są przeplatane. Na przykład, jeśli generator wykonuje jakieś obliczenia, a program wywołujący wydrukuje wyniki, zobaczysz wyniki, gdy tylko będą dostępne. Jest to forma współbieżności.
Ta analogia nie jest jednak niczym szczególnym
yield from
- jest raczej ogólną właściwością generatorów w Pythonie.źródło
get_list_values_as_xxx
są to proste generatory z jedną liniąfor x in input_param: yield int(x)
i dwoma pozostałymi odpowiednio zstr
ifloat
Gdziekolwiek wywołać generatora od wewnątrz generatora trzeba „pompa”, aby ponownie
yield
wartości:for v in inner_generator: yield v
. Jak wskazuje PEP, istnieją subtelne zawiłości, które większość ludzi ignoruje. Nielokalna kontrola przepływu, podobnie jakthrow()
jeden przykład podany w PEP. Nowa składniayield from inner_generator
jest używana wszędzie tam, gdzie wcześniej napisałeś wyraźnąfor
pętlę. Nie jest to jednak tylko cukier składniowy: obsługuje wszystkie narożne przypadki, które są ignorowane przezfor
pętlę. Bycie „słodkim” zachęca ludzi do korzystania z niego, a tym samym do uzyskania właściwych zachowań.Ta wiadomość w wątku dyskusyjnym mówi o tych złożonościach:
Nie mogę porozmawiać o porównaniu z mikrowątkami poza obserwacją, że generatory są rodzajem paralelizmu. Możesz uznać zawieszony generator za wątek, który przesyła wartości
yield
do wątku klienta. Rzeczywista implementacja może być podobna do tej (a rzeczywista implementacja jest oczywiście bardzo interesująca dla programistów Python), ale nie dotyczy to użytkowników.Nowa
yield from
składnia nie dodaje do języka żadnych dodatkowych możliwości w zakresie wątków, po prostu ułatwia prawidłowe korzystanie z istniejących funkcji. Mówiąc dokładniej, nowicjuszowi złożonemu generatorowi wewnętrznemu napisanemu przez eksperta łatwiej jest przejść przez ten generator bez naruszania jego złożonych funkcji.źródło
Krótki przykład pomoże ci zrozumieć jeden z
yield from
przypadków użycia: uzyskaj wartość z innego generatoraźródło
print(*flatten([1, [2], [3, [4]]]))
yield from
w zasadzie efektywnie łączy iteratory:Jak widać, usuwa jedną czystą pętlę Pythona. To prawie wszystko, co robi, ale tworzenie łańcuchów iteratorów jest dość powszechnym wzorcem w Pythonie.
Wątki są w zasadzie funkcją, która pozwala wyskoczyć z funkcji w całkowicie losowych punktach i wrócić do stanu innej funkcji. Nadzorca wątku robi to bardzo często, więc program wydaje się uruchamiać wszystkie te funkcje jednocześnie. Problem polega na tym, że punkty są losowe, więc musisz użyć blokowania, aby zapobiec zatrzymaniu funkcji przez przełożonego w problematycznym punkcie.
Generatory są bardzo podobne do wątków w tym sensie: pozwalają określić określone punkty (za każdym razem, gdy
yield
), w których można wskakiwać i wyskakiwać. Gdy są używane w ten sposób, generatory nazywane są coroutines.Przeczytaj te doskonałe samouczki na temat coroutines w Pythonie, aby uzyskać więcej informacji
źródło
throw()/send()/close()
są toyield
funkcje, któreyield from
oczywiście muszą zostać poprawnie zaimplementowane, ponieważ mają uprościć kod. Takie trywialności nie mają nic wspólnego z użytkowaniem.chain
funkcji, ponieważitertools.chain
już istnieje. Zastosowanieyield from itertools.chain(*iters)
.W zastosowaniu do asynchronicznej IO coroutine ,
yield from
ma podobne zachowanie jakawait
w funkcji coroutine . Oba są wykorzystywane do zawieszenia wykonywania koroutyny.yield from
jest używany przez coroutine opartą na generatorze .await
jest stosowany w przypadkuasync def
rogówki. (od Python 3.5+)W przypadku Asyncio, jeśli nie ma potrzeby obsługi starszej wersji języka Python (tj.> 3.5),
async def
/await
jest zalecaną składnią do zdefiniowania coroutine. Wyield from
ten sposób nie jest już potrzebny w rogówce.Ale ogólnie poza asyncio,
yield from <sub-generator>
ma jeszcze inne zastosowanie w iteracji pod-generatora, jak wspomniano we wcześniejszej odpowiedzi.źródło
Ten kod definiuje funkcję
fixed_sum_digits
zwracającą generator zliczający wszystkie sześć cyfr, tak że suma cyfr wynosi 20.Spróbuj napisać bez
yield from
. Jeśli znajdziesz skuteczny sposób, aby to zrobić, daj mi znać.Myślę, że w przypadkach takich jak ten: odwiedzanie drzew
yield from
czyni kod prostszym i czystszym.źródło
Mówiąc prosto,
yield from
zapewnia rekurencję ogona dla funkcji iteratora.źródło