W jaki sposób utworzyć funkcję iteracyjną (lub obiekt iteracyjny) w pythonie?Tworzenie podstawowego Iteratora Pythona
Odpowiedz
Obiekty Iteratora w pythonie są zgodne z protokołem iteratora, co w zasadzie oznacza, że zapewniają dwie metody: __iter__()
i next()
. __iter__
zwraca obiekt iteratora i jest niejawnie wywoływany na początku pętli. Metoda next()
zwraca następną wartość i jest niejawnie wywoływana dla każdego przyrostu pętli. next()
podnosi wyjątek StopIteration, gdy nie ma więcej wartości do zwrócenia, która jest niejawnie przechwytywana przez zapętlenie konstrukcji w celu zatrzymania iteracji.
Oto prosty przykład licznika:
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
print c
ten wypisze:
3
4
5
6
7
8
Łatwiej to napisać przy użyciu generatora, objętych w poprzedniej odpowiedzi:
def counter(low, high):
current = low
while current <= high:
yield current
current += 1
for c in counter(3, 8):
print c
Wydrukowane wydruki będą takie same. Pod maską obiekt generatora obsługuje protokół iteratora i robi coś z grubsza podobnego do klasy Counter.
Artykuł Davida Mertza, Iterators and Simple Generators, jest całkiem niezłym wprowadzeniem.
Zauważ, że funkcja 'next()' nie ma wartości 'yield',' zwraca 'je. –
To jest nieprawidłowe w Pythonie 3 --- musi to być '__next __()'. – Aerovistae
Jest to w większości dobra odpowiedź, ale fakt, że wraca on sam, jest trochę mniej optymalny. Na przykład, jeśli użyjesz tego samego obiektu licznika w podwójnie zagnieżdżonej pętli for, prawdopodobnie nie otrzymasz takiego zachowania, jakie miałeś na myśli. –
Przede wszystkim itertools module jest niezwykle przydatna dla wszystkich rodzajów spraw, w których iterator byłyby użyteczne, ale tutaj jest wszystko, czego potrzeba, aby utworzyć iteracyjnej w python:
wydajność
Czy to nie jest fajne? Wydajność może być użyta w celu zastąpienia zwykłej funkcji powrotu. Zwraca obiekt tak samo, ale zamiast niszczyć stan i wychodzić, zapisuje stan, gdy chcesz wykonać następną iterację. Oto przykład jak to działa wyciągnął bezpośrednio z itertools function list:
def count(n=0):
while True:
yield n
n += 1
Jak podano w opisie funkcji (jest to count() funkcji z modułu itertools ...), to powoduje, że iterator zwraca kolejne liczby całkowite zaczynające się od n.
Generator expressions to cała inna puszka robaków (niesamowite robale!). Mogą być użyte zamiast List Comprehension, aby zaoszczędzić pamięć (spisane listy tworzą listę w pamięci, która jest zniszczona po użyciu, jeśli nie jest przypisana do zmiennej, ale wyrazy generatora mogą tworzyć obiekt generatora ... który jest fantazyjnym sposobem powiedzenia Iterator). Oto przykład określenia ekspresji generatora:
gen = (n for n in xrange(0,11))
ta jest bardzo podobna do iteracyjnej naszą definicją powyżej, z wyjątkiem pełnego zakresu jest ustalona na poziomie między 0 a 10.
właśnie się XRange() (zaskoczony, że nie widziałem go wcześniej ...) i dodałem go do powyższego przykładu. xrange() to iterowalna wersja zakresu (), która ma tę zaletę, że nie jest przygotowana do utworzenia listy. Byłoby bardzo przydatne, gdybyś miał gigantyczny korpus danych do iteracji i miał tylko tyle pamięci, aby to zrobić.
Istnieją cztery sposoby tworzenia iteracyjne funkcję:
- utworzenia generatora (używa yield keyword)
- użycie wyrażenia generującego (genexp)
- tworzyć iterację (definicja
__iter__
and__next__
(lubnext
w Pythonie 2.x)) - utworzyć funkcję, którą Python może sam przerobić (defines
__getitem__
)
Przykłady:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
żeby zobaczyć wszystkie cztery metody w akcji:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print ch,
print
co skutkuje:
A B C D E
A B C D E
A B C D E
A B C D E
Uwaga:
Dwa generatora typy (uc_gen
i uc_genexp
) nie mogą być reversed()
; zwykły iterator (uc_iter
) wymagałby magicznej metody (która musi zwracać nowy iterator, który cofa się); a GetItem iteracyjnych (uc_getitem
) musi mieć metodę __len__
Magic:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
Aby odpowiedzieć na pytanie wtórnego pułkownika panika jest o nieskończonej leniwie ocenianego iterator, tutaj są te przykłady, używając każdej z czterech powyższych metod:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
co powoduje (przynajmniej dla mojego biegu próbek):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Podoba mi się to podsumowanie, ponieważ jest kompletne. Te trzy sposoby (wydajność, ekspresja generatora i iterator) są zasadniczo takie same, chociaż niektóre są wygodniejsze niż inne. Operator yield przechwytuje "kontynuację", która zawiera stan (na przykład indeks, do którego jesteśmy uprawnieni). Informacje są zapisywane w "zamknięciu" kontynuacji. Sposób iteracji zapisuje te same informacje w polach iteratora, który jest w zasadzie tym samym co zamknięcie. Metoda __getitem__ jest trochę inna, ponieważ indeksuje zawartość i nie ma charakteru iteracyjnego. – Ian
Nie zwiększasz indeksu w twoim ostatnim podejściu, 'uc_getitem()'.Właściwie podczas refleksji nie powinien zwiększać indeksu, ponieważ go nie obsługuje. Ale to także nie jest sposób na abstrakcyjną iterację. –
@metaperl: Właściwie to jest. We wszystkich czterech powyższych przypadkach możesz użyć tego samego kodu do iteracji. –
Widzę niektórych z was robi return self
w __iter__
. Chciałem tylko zwrócić uwagę, że sama __iter__
może być generator (usuwając w ten sposób potrzebę __next__
i podnoszenie StopIteration
wyjątkami)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Oczywiście tutaj jeden równie dobrze może bezpośrednio dokonać generator, ale w przypadku bardziej skomplikowanych klas to możliwe być przydatnym.
Świetnie! To takie nudne pisanie właśnie 'return self' w' __iter__'. Kiedy miałem zamiar spróbować użyć 'yield' w tym pliku znalazłem twój kod robiący dokładnie to, co chcę wypróbować. – Ray
Ale w takim przypadku, w jaki sposób można wdrożyć 'next()'? 'return iter (self) .next()'? – Lenna
@Lenna, jest już "zaimplementowana", ponieważ iter (self) zwraca iterator, a nie instancję zasięgu. – Manux
Jest to funkcja iteracyjna bez yield
. To sprawi, że korzystanie z funkcji iter
i zamknięcia, który utrzymuje, że to państwo w sposób zmienny (list
) w zamykającym zakres python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
dla Pythona 3, stan zamknięcia jest przechowywany w niezmiennym w zakresie osłaniającego i nonlocal
jest używany w zakresie lokalnym, aby zaktualizować zmienną stanu.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Test;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
Zawsze doceniam sprytne użycie dwóch argumentów 'iter', ale dla jasności: jest to bardziej złożone i mniej wydajne niż użycie funkcji generującej opartej na" wydajności "; Python ma mnóstwo interpreterów wspierających funkcje generujące 'yield', których nie można tu wykorzystać, co znacznie spowalnia ten kod. Wciąż przegłosowywany. – ShadowRanger
To pytanie dotyczy obiektów iteracyjnych, a nie iteratorów. W języku Python sekwencje są również iterowalne, więc jednym ze sposobów utworzenia klasy iterowalnej jest sprawienie, aby zachowywała się jak sekwencja, tj. Nadawanie jej metod. Przetestowałem to na Pythonie 2 i 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
Tutaj są dwa pytania, oba ważne. Jak sprawić, by klasa była iterowalna (tzn. I jak utworzyć funkcję, która zwraca sekwencję z leniwą oceną? –
Dobre ćwiczenie myślę, że napisać klasę, która reprezentuje liczby parzyste (nieskończona sekwencja). –
@ColonelPanic: Okay, dodano przykład nieskończonej liczby do [moja odpowiedź] (http://stackoverflow.com/a/7542261/208880). –