Jak utworzyć funkcję iteracyjną (lub obiekt iteratora) w Pythonie?
Obiekty iteratora w pythonie są zgodne z protokołem iteratora, co w zasadzie oznacza, że zapewniają dwie metody: __iter__()
i __next__()
.
__iter__
Zwraca obiekt iteracyjnej i nazywany jest niejawnie na początku pętli.
__next__()
Sposób powraca następną wartość zwana jest pośrednio na każde kolejne pętli. Ta metoda wywołuje wyjątek StopIteration, gdy nie ma już wartości do zwrócenia, która jest domyślnie przechwytywana przez zapętlone konstrukcje, aby zatrzymać iterację.
Oto prosty przykład licznika:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Spowoduje to wydrukowanie:
3
4
5
6
7
8
Łatwiej jest pisać za pomocą generatora, jak opisano w poprzedniej odpowiedzi:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Wydruk będzie taki sam. Pod maską obiekt generatora obsługuje protokół iteratora i robi coś mniej więcej podobnego do klasy Counter.
Artykuł Davida Mertza, Iterators and Simple Generators , jest całkiem dobrym wstępem.
__next__
.counter
jest iteratorem, ale nie jest sekwencją. Nie przechowuje swoich wartości. Na przykład nie powinieneś używać licznika w podwójnie zagnieżdżonej pętli for.__iter__
(oprócz in__init__
). W przeciwnym razie obiekt można powtórzyć tylko raz. Na przykład, jeśli powieszctr = Counters(3, 8)
, nie możesz użyćfor c in ctr
więcej niż raz.Counter
jest iteratorem, a iteratory powinny być iterowane tylko raz. Po zresetowaniuself.current
w__iter__
, a następnie pętla zagnieżdżona nadCounter
byłyby całkowicie uszkodzony, i wszelkiego rodzaju przyjętych zachowań iteratorów (który dzwoniiter
na nich jest idempotent) zostały naruszone. Jeśli chcesz mieć możliwość iteracjictr
więcej niż jeden raz, musi to być iterator bez iteracji, w którym zwraca za każdym razem zupełnie nowy iterator__iter__
. Próba mieszania i dopasowywania (iterator, który jest domyślnie resetowany po__iter__
wywołaniu) narusza protokoły.Counter
ma być iterowalny bez iteratora, usunąłbyś definicję__next__
/next
całkowicie i prawdopodobnie przedefiniowałbyś__iter__
funkcję generatora o tej samej formie co generator opisany na końcu tej odpowiedzi (z wyjątkiem zamiast granic pochodzących z argumentów__iter__
, że będą argumenty__init__
zapisywane naself
i dostępne odself
w__iter__
).Istnieją cztery sposoby na zbudowanie funkcji iteracyjnej:
__iter__
i__next__
(lubnext
w Python 2.x))__getitem__
)Przykłady:
Aby zobaczyć wszystkie cztery metody w akcji:
Co skutkuje w:
Uwaga :
Dwa typy generatorów (
uc_gen
iuc_genexp
) nie mogą byćreversed()
; zwykły iterator (uc_iter
) potrzebowałby__reversed__
magicznej metody (która, zgodnie z dokumentacją , musi zwrócić nowy iterator, ale zwracanieself
działa (przynajmniej w CPython)); a getitem iteratable (uc_getitem
) musi mieć__len__
metodę magiczną:Aby odpowiedzieć na drugorzędne pytanie pułkownika Paniki dotyczące nieskończonego, leniwie ocenianego iteratora, oto te przykłady, wykorzystujące każdą z czterech powyższych metod:
Które wyniki (przynajmniej dla mojej próbki):
Jak wybrać, którego użyć? Jest to głównie kwestia gustu. Dwie najczęściej spotykane metody to generatory i protokół iteratora, a także hybryda (
__iter__
zwracanie generatora).Wyrażenia generatora są przydatne do zastępowania wyrażeń listowych (są leniwe i mogą oszczędzać zasoby).
Jeśli potrzebna jest kompatybilność z wcześniejszymi wersjami Python 2.x użyj
__getitem__
.źródło
uc_iter
powinna wygasnąć po jej zakończeniu (w przeciwnym razie nastąpiłoby to w nieskończoność); jeśli chcesz to zrobić ponownie, musisz uzyskać nowy iterator, dzwoniącuc_iter()
ponownie.self.index = 0
w__iter__
tak, że można iteracyjne wiele razy. W przeciwnym razie nie możesz.Przede wszystkim moduł itertools jest niezwykle przydatny w różnego rodzaju przypadkach, w których przydatny byłby iterator, ale oto wszystko, czego potrzebujesz, aby utworzyć iterator w pythonie:
Czy to nie fajne? Wydajność może być wykorzystana do zastąpienia normalnego powrotu w funkcji. Zwraca obiekt tak samo, ale zamiast niszczyć stan i wychodzić, zapisuje stan na wypadek, gdy chcesz wykonać następną iterację. Oto przykład tego działania pobranego bezpośrednio z listy funkcji itertools :
Jak podano w opisie funkcji (jest to funkcja count () z modułu itertools ...), tworzy iterator, który zwraca kolejne liczby całkowite zaczynające się od n.
Wyrażenia generatora to zupełnie inna puszka robaków (niesamowite robaki!). Mogą być używane zamiast Zrozumienia listy w celu oszczędzania pamięci (wyrazy z listy tworzą listę w pamięci, która ulega zniszczeniu po użyciu, jeśli nie jest przypisana do zmiennej, ale wyrażenia generatora mogą tworzyć Obiekt Generatora ... co jest fantazyjnym sposobem mówiąc Iterator). Oto przykład definicji wyrażenia generatora:
Jest to bardzo podobne do powyższej definicji iteratora, z tym wyjątkiem, że pełny zakres jest z góry określony między 0 a 10.
Właśnie znalazłem xrange () (zaskoczony, że nie widziałem go wcześniej ...) i dodałem go do powyższego przykładu. xrange () jest iterowalną wersją range (), która ma tę zaletę, że nie buduje listy wcześniej. Byłoby bardzo przydatne, gdybyś miał gigantyczny zbiór danych do iteracji i miał tylko tyle pamięci, aby to zrobić.
źródło
Widzę, że niektórzy z was robi
return self
w__iter__
. Chciałem tylko zauważyć, że__iter__
sam może być generatorem (eliminując w ten sposób potrzebę__next__
i podnoszącStopIteration
wyjątki)Oczywiście tutaj równie dobrze można stworzyć generator, ale w przypadku bardziej złożonych klas może być on użyteczny.
źródło
return self
w__iter__
. Kiedy chciałemyield
w nim użyć , znalazłem twój kod robiąc dokładnie to, co chcę spróbować.next()
?return iter(self).next()
?self.current
Ani żadnego innego licznika. To powinna być najczęściej głosowana odpowiedź!iter
instancje klasy, ale one same nie są instancjami klasy.To pytanie dotyczy obiektów iterowalnych, a nie iteratorów. W Pythonie sekwencje też są iterowalne, więc jednym ze sposobów na stworzenie klasy iterowalnej jest sprawienie, aby zachowywała się jak sekwencja, tj. Podanie jej
__getitem__
i__len__
metod. Przetestowałem to na Python 2 i 3.źródło
__len__()
metody.__getitem__
sam z oczekiwanym zachowaniem jest wystarczający.Wszystkie odpowiedzi na tej stronie są naprawdę świetne dla złożonego obiektu. Ale dla tych, które zawierają wbudowane iterowalny typów jako atrybuty, takie jak
str
,list
,set
lubdict
, albo dowolny realizacjacollections.Iterable
można pominąć pewne rzeczy w swojej klasie.Może być używany jak:
źródło
return iter(self.string)
.Jest to funkcja powtarzalna bez
yield
. Wykorzystujeiter
funkcję i zamknięcie, które utrzymuje jej stan w zmiennej (list
) w zakresie obejmującym python 2.W Pythonie 3 stan zamknięcia jest niezmienny w zakresie obejmującym i
nonlocal
jest używany w zasięgu lokalnym do aktualizacji zmiennej stanu.Test;
źródło
iter
, ale dla jasności: jest to bardziej złożone i mniej wydajne niż zwykłe korzystanie zyield
funkcji generatora; Python oferuje mnóstwo interpreterów do obsługiyield
funkcji generatora, których nie można tutaj wykorzystać, co znacznie spowalnia ten kod. Mimo to głosowano.Jeśli szukasz czegoś krótkiego i prostego, może ci to wystarczy:
przykład użycia:
źródło
Zainspirowany odpowiedzią Matt Gregory tutaj jest nieco bardziej skomplikowany iterator, który zwróci a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
źródło