Dlaczego iteratory w Pythonie zgłaszają wyjątek?

48

Oto składnia iteratorów w Javie (nieco podobna składnia w C #):

Iterator it = sequence.iterator();

while (it.hasNext()) {
    System.out.println(it.next());
}

Co ma sens. Oto równoważna składnia w Pythonie:

it = iter(sequence)
while True:
    try:
        value = it.next() 
    except StopIteration:
        break
    print(value)

Myślałem, że wyjątki powinny być stosowane tylko w wyjątkowych okolicznościach.

Dlaczego Python używa wyjątków, aby zatrzymać iterację?

NullUserException
źródło

Odpowiedzi:

50

Istnieje bardzo Pythoniczny sposób na napisanie tego wyrażenia bez jawnego pisania bloku try-wyjątkiem dla StopIteration:

# some_iterable is some collection that can be iterated over
# e.g., a list, sequence, dict, set, itertools.combination(...)

for value in some_iterable:
    print(value)

Możesz przeczytać o odpowiednich dokumentach PEP 234 255, jeśli chcesz dowiedzieć się więcej o przyczynach StopIterationwprowadzenia i logice iteratorów.

Ogólną zasadą w pythonie jest posiadanie jednego sposobu zrobienia czegoś (patrz import this), a najlepiej jego piękna, wyraźna, czytelna i prosta metoda, którą spełnia metoda pythonowa. Twój równoważny kod jest konieczny tylko dlatego, że Python nie daje iteratorom hasNextfunkcji członka; wolę, aby ludzie po prostu bezpośrednio przechodzili przez iteratory (a jeśli musisz zrobić coś innego, po prostu spróbuj je przeczytać i złapać wyjątek).

Automatyczne przechwytywanie StopIterationwyjątku na końcu iteratora ma sens i jest analogią do EOFErrorpodniesionego, jeśli czytasz poza koniec pliku.

dr jimbob
źródło
6
Sposób „Pythoniczny” wydaje się bardziej podobny do „wartości w sekwencji:” niż „wartości w iter (sekwencji):”. Czy zaktualizować wpis?
Yam Marcovic,
15
@Yam: zgadzam się. Nie jest pytoniczne, aby pobierać istniejącą sekwencję i przekształcać ją w iterator, aby zastosować do niej pętlę for; sekwencja jest już iterowalna, więc konwersja a listna a listiteratorjest bezcelowa. Ciągle pierwszą linię tylko podążać za punkt wyjścia NullUserException, aby wyjaśnić, w jaki sposób powinien pętli nad iteratora, który jest taki sam sposób, w jaki powinien pętla nad każdym iterable ( list, set, str, tuple, dict, file, generator, itd.). Mógłbym zrobić coś takiego, it = itertools.combinations("ABCDE", 2)aby uzyskać lepszy przykład znaczącego iteratora.
dr jimbob
1
it = iter(sequence)nie jest potrzebne.
Caridorc,
2
@Caridorc - Jeśli czytasz komentarze, to Twój punkt został rozwiązany. Nie jest to konieczne i zostało zrobione, aby podążać za punktem początkowym pytania (gdzie wyraźnie pytano iterators) i musisz iterjawnie wygenerować iterator(try type([])( list) vs type(iter([]))( listiterator)).
dr jimbob
//, @drjimbob, podnosisz doskonały punkt w drugim komentarzu do tego pytania. Jestem trochę nowy w zaawansowanych funkcjach iteracyjnych i nie pochwyciłbym tego, gdybym nie przeczytał komentarzy. Myślę, że przydałoby się wielu z nas biednych, samokształcących się dusz, gdybyśmy mogli dostrzec ten punkt, w jaki sposób samo pytanie można zmienić na „The Python Way” jako pierwszą, główną część odpowiedzi.
Nathan Basanese
28

Powód, dla którego Python używa wyjątku do zatrzymania iteracji, jest udokumentowany w PEP 234 :

Kwestionowano, czy wyjątek sygnalizujący koniec iteracji nie jest zbyt drogi. Zaproponowano kilka alternatyw dla wyjątku StopIteration: specjalna wartość End sygnalizująca koniec, funkcja end () do sprawdzania, czy iterator jest zakończony, a nawet ponowne użycie wyjątku IndexError.

  • Specjalna wartość ma problem polegający na tym, że jeśli sekwencja kiedykolwiek zawiera tę specjalną wartość, pętla nad tą sekwencją zakończy się przedwcześnie bez żadnego ostrzeżenia. Jeśli doświadczenie z zakończonymi zerem łańcuchami C nie nauczyło nas problemów, jakie może to powodować, wyobraź sobie kłopot, jaki miałoby narzędzie introspekcji Pythona podczas iteracji na liście wszystkich wbudowanych nazw, zakładając, że specjalna wartość End była wbudowaną w imię!

  • Wywołanie funkcji end () wymagałoby dwóch wywołań na iterację. Dwa połączenia są znacznie droższe niż jedno połączenie plus test na wyjątek. Zwłaszcza krytyczna czasowo pętla może bardzo tanio przetestować wyjątek.

  • Ponowne użycie IndexError może powodować zamieszanie, ponieważ może to być prawdziwy błąd, który zostałby zamaskowany przez przedwczesne zakończenie pętli.

Uwaga: idiomatyczny sposób Pythona do zapętlania sekwencji jest następujący:

for value in sequence:
    print (value)
lesmana
źródło
20

To różnica w filozofii. Filozofią projektowania Python jest EAFP :

Łatwiej prosić o wybaczenie niż o pozwolenie. Ten wspólny styl kodowania w języku Python zakłada istnienie prawidłowych kluczy lub atrybutów i wychwytuje wyjątki, jeśli założenie okaże się fałszywe. Ten czysty i szybki styl charakteryzuje się obecnością wielu tryi exceptwypowiedzi. Ta technika kontrastuje ze stylem LBYL wspólnym dla wielu innych języków, takich jak C ...

Charles E. Grant
źródło
7

Po prostu implementacja Java ma hasNext()metodę umożliwiającą sprawdzenie pustego iteratora przed wykonaniem next(). Gdy wywołujesz next()iterator Java bez pozostawionych elementów, NoSuchElementExceptiongenerowane jest a .

Tak skutecznie możesz wykonać try..catch w Javie jak try..except w Pythonie. I tak, zgodnie z poprzednią odpowiedzią, filozofia jest bardzo ważna w świecie Python.

yati sagade
źródło