Aby odpowiedzieć na pytanie, gdzie StopIteration
wpada w generator utworzony wewnątrz itertools.tee
: nie. Odbiorca musi odnieść się do wyników tee
, aby wychwycić wyjątek podczas ich iteracji.
Po pierwsze, należy zauważyć, że funkcja generatora (która jest dowolną funkcją z instrukcją yield
w dowolnym miejscu) różni się zasadniczo od normalnej funkcji. Zamiast uruchamiać kod funkcji po wywołaniu, otrzymasz po prostu obiekt generator
podczas wywoływania funkcji. Tylko wtedy, gdy wykonasz iterację nad generatorem, uruchomisz kod.
Funkcja generatora nigdy nie zakończy iterowania bez podniesienia StopIteration
(chyba że podniesie to inny wyjątek). StopIteration
to sygnał z generatora, że jest on wykonywany i nie jest opcjonalny. Jeśli osiągniesz instrukcję return
lub koniec kodu funkcji generatora bez podnoszenia czegokolwiek, Python podniesie dla ciebie StopIteration
!
To różni się od zwykłych funkcji, które zwracają None
, jeśli osiągną koniec bez zwracania czegokolwiek innego. Łączy się z różnymi sposobami działania generatorów, jak opisałem powyżej.
Oto przykład funkcja generator, który sprawi, że łatwo zobaczyć, jak StopIteration
zostanie podniesiony:
def simple_generator():
yield "foo"
yield "bar"
# StopIteration will be raised here automatically
Oto co się dzieje, kiedy go spożywać:
>>> g = simple_generator()
>>> next(g)
'foo'
>>> next(g)
'bar'
>>> next(g)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(g)
StopIteration
Wywołanie simple_generator
zawsze zwraca generator
obiektu natychmiast (bez uruchamiania żadnego kodu w funkcji). Każde wywołanie next
w obiekcie generatora uruchamia kod do następnej instrukcji yield
i zwraca uzyskaną wartość. Jeśli nie ma więcej, aby uzyskać, podnosi się StopIteration
.
Obecnie zwykle nie są wyświetlane wyjątki od StopIteration
. Powodem tego jest fakt, że zwykle zużywasz generatory wewnątrz pętli for
. Oświadczenie for
automatycznie wywoła next
w kółko, aż zostanie podniesione StopIteration
. To złapie i zlikwiduje dla ciebie wyjątek StopIteration
, więc nie musisz zmywać z blokami try
/except
, aby sobie z tym poradzić.
for
pętla jak for item in iterable: do_suff(item)
jest prawie dokładnie równoważne z niniejszym while
pętli (jedyna różnica polega na tym, że prawdziwa for
nie potrzebuje zmienną tymczasową trzymać iterator):
iterator = iter(iterable)
try:
while True:
item = next(iterator)
do_stuff(item)
except StopIteration:
pass
finally:
del iterator
Funkcja gen
generator pokazany u góry jest jednym wyjątkiem. Używa on wyjątku StopIteration
wytwarzanego przez iterator, który zużywa jako swój własny sygnał, że jest wykonywany na iteracji. Oznacza to, że zamiast wychwytywania StopIteration
, a następnie zerwania z pętli, po prostu pozwala, aby wyjątek nie był przechwytywany (prawdopodobnie zostanie przechwycony przez jakiś kod wyższego poziomu).
Niezwiązane z głównym pytaniem, jest jeszcze jedna rzecz, którą chcę wskazać. W swoim kodzie wywołujesz next
dla zmiennej o nazwie iterable
. Jeśli weźmiesz to nazwisko jako dokumentację, jaki typ obiektu otrzymasz, nie jest to konieczne.
next
jest częścią protokołu iterator
, a nie iterable
(lub container
). Może działać dla niektórych rodzajów iteracji (takich jak pliki i generatory, ponieważ te typy są ich własnymi iteratorami), ale nie powiedzie się dla innych iteracji, takich jak krotki i listy. Im bardziej poprawne podejście, jest wywołanie iter
na wartości iterable
, a następnie wywołanie next
w otrzymanym iteratorze. (Lub po prostu użyć for
pętle, które wymagają zarówno iter
i next
dla Ciebie w odpowiednim czasie!)
Edit: Właśnie znalazłem moje własne odpowiedzi w wyszukiwarce Google dla powiązanego pytanie, a ja myślałem, że aktualizacji do punktu że powyższa odpowiedź nie będzie całkowicie prawdziwa w przyszłych wersjach Pythona. PEP 479 powoduje błąd polegający na tym, że StopIteration
zapala się nieprzytomnie z funkcji generatora. Jeśli tak się stanie, Python zmieni go w wyjątek RuntimeError
.
Oznacza to, że kod taki jak przykłady w itertools
, które używają StopIteration
do wyrwania się z funkcji generatora, będzie wymagał modyfikacji. Zazwyczaj będziesz musiał złapać wyjątek z try
/except
, a następnie return
.
Ponieważ jest to niezgodna wstecz zmiana, jest ona stopniowo wprowadzana. W Pythonie 3.5, cały kod będzie działał jak poprzednio, ale możesz uzyskać nowe zachowanie za pomocą from __future__ import generator_stop
. W Pythonie 3.6 kod nadal będzie działał, ale wyświetli ostrzeżenie. W Pythonie 3.7 nowe zachowanie będzie obowiązywać przez cały czas.
Jak się nazywasz? –