Jaka jest różnica między iteratorami a generatorami? Przydałoby się kilka przykładów użycia każdego przypadku.
Jaka jest różnica między iteratorami a generatorami? Przydałoby się kilka przykładów użycia każdego przypadku.
Odpowiedzi:
iterator
jest bardziej ogólną koncepcją: każdy obiekt, którego klasa ma next
metodę ( __next__
w Pythonie 3) i __iter__
metodę, która ją posiada return self
.
Każdy generator jest iteratorem, ale nie odwrotnie. Generator jest zbudowany przez wywołanie funkcji, która ma jedno lub więcej yield
wyrażeń ( yield
instrukcji w Pythonie 2.5 i wcześniejszych) i jest obiektem spełniającym definicję an z poprzedniego akapitu iterator
.
Możesz użyć niestandardowego iteratora zamiast generatora, gdy potrzebujesz klasy o nieco złożonym zachowaniu utrzymującym stan lub chcesz ujawnić inne metody oprócz next
( __iter__
i __init__
). Najczęściej generator (czasami, dla wystarczająco prostych potrzeb, wyrażenie generatora ) jest wystarczający i jest łatwiejszy do kodowania, ponieważ utrzymanie stanu (w rozsądnych granicach) jest w zasadzie „wykonywane dla ciebie” przez zawieszenie i wznowienie ramki.
Na przykład generator, taki jak:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
lub równoważne wyrażenie generatora (genexp)
generator = (i*i for i in range(a, b))
zajęłoby więcej kodu do zbudowania jako niestandardowy iterator:
class Squares(object):
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self): return self
def next(self): # __next__ in Python 3
if self.start >= self.stop:
raise StopIteration
current = self.start * self.start
self.start += 1
return current
iterator = Squares(a, b)
Ale oczywiście z klasą Squares
można łatwo zaoferować dodatkowe metody, tj
def current(self):
return self.start
jeśli rzeczywiście potrzebujesz takiej dodatkowej funkcjonalności w swojej aplikacji.
for ... in ...:
, albo funkcja, albo zadzwonisziter.next()
for..in
składni. Może czegoś mi brakowało, ale to było jakiś czas temu, nie pamiętam, czy rozwiązałem. Dziękuję Ci!
Jaka jest różnica między iteratorami a generatorami? Przydałoby się kilka przykładów użycia każdego przypadku.
Podsumowując: Iteratory to obiekty, które mają metodę __iter__
i __next__
( next
w Pythonie 2). Generatory zapewniają łatwy, wbudowany sposób tworzenia instancji Iteratorów.
Funkcja z wydajnością jest nadal funkcją, która po wywołaniu zwraca instancję obiektu generatora:
def a_function():
"when called, returns generator object"
yield
Wyrażenie generatora zwraca również generator:
a_generator = (i for i in range(0))
Aby uzyskać bardziej szczegółową ekspozycję i przykłady, czytaj dalej.
W szczególności generator jest podtypem iteratora.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Generator możemy stworzyć na kilka sposobów. Bardzo powszechnym i prostym sposobem na to jest funkcja.
W szczególności funkcja z wydajnością jest funkcją, która po wywołaniu zwraca generator:
>>> def a_function():
"just a function definition with yield in it"
yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function() # when called
>>> type(a_generator) # returns a generator
<class 'generator'>
Generator znowu jest iteratorem:
>>> isinstance(a_generator, collections.Iterator)
True
Iterator to Iterable,
>>> issubclass(collections.Iterator, collections.Iterable)
True
co wymaga __iter__
metody zwracającej Iterator:
>>> collections.Iterable()
Traceback (most recent call last):
File "<pyshell#79>", line 1, in <module>
collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__
Niektóre przykłady iterable to wbudowane krotki, listy, słowniki, zestawy, zestawy zamrożone, ciągi, ciągi bajtów, tablice bajtów, zakresy i widoki pamięci:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
lub __next__
metodyW Python 2:
>>> collections.Iterator()
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next
A w Python 3:
>>> collections.Iterator()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__
Możemy pobrać iteratory z wbudowanych obiektów (lub obiektów niestandardowych) za pomocą iter
funkcji:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
__iter__
Metoda jest wywoływana, gdy spróbujesz użyć obiektu w pętli for. Następnie __next__
metoda jest wywoływana na obiekcie iteratora w celu pobrania każdego elementu do pętli. Iterator podnosi się, StopIteration
gdy go wyczerpałeś i nie można go ponownie użyć w tym momencie.
Z sekcji Typy generatorów w sekcji Typy iteratorów w dokumentacji Typów wbudowanych :
Generatory Pythona zapewniają wygodny sposób implementacji protokołu iteratora. Jeśli
__iter__()
metoda obiektu kontenerowego zostanie zaimplementowana jako generator, automatycznie zwróci obiekt iteratora (technicznie rzecz biorąc, obiekt generatora) dostarczający metody__iter__()
inext()
[__next__()
w Pythonie 3]. Więcej informacji na temat generatorów można znaleźć w dokumentacji wyrażenia wydajności.
(Podkreślenie dodane.)
Z tego dowiadujemy się, że Generatory to (wygodny) typ Iteratora.
Możesz utworzyć obiekt, który implementuje protokół Iterator, tworząc lub rozszerzając własny obiekt.
class Yes(collections.Iterator):
def __init__(self, stop):
self.x = 0
self.stop = stop
def __iter__(self):
return self
def next(self):
if self.x < self.stop:
self.x += 1
return 'yes'
else:
# Iterators must raise when done, else considered broken
raise StopIteration
__next__ = next # Python 3 compatibility
Ale łatwiej jest po prostu użyć Generatora, aby to zrobić:
def yes(stop):
for _ in range(stop):
yield 'yes'
Lub może prościej, Wyrażenie Generatora (działa podobnie do wyrażeń listowych):
yes_expr = ('yes' for _ in range(stop))
Wszystkie mogą być używane w ten sam sposób:
>>> stop = 4
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop),
('yes' for _ in range(stop))):
... print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes
Możesz użyć protokołu Iterator bezpośrednio, gdy potrzebujesz rozszerzyć obiekt Python jako obiekt, który można iterować.
Jednak w zdecydowanej większości przypadków najlepiej jest użyć yield
do zdefiniowania funkcji, która zwraca Iterator Generatora lub rozważenia Wyrażeń Generatora.
Na koniec zauważ, że generatory zapewniają jeszcze większą funkcjonalność jako coroutines. Wyjaśniam Generatory wraz ze yield
stwierdzeniem dogłębnie w mojej odpowiedzi na „Co robi słowo kluczowe„ wydajność ”?”.
Iteratory:
Iterator to obiekty, które wykorzystują next()
metodę do uzyskania następnej wartości sekwencji.
Generatory:
Generator to funkcja, która generuje lub generuje sekwencję wartości przy użyciu yield
metody.
Każde next()
wywołanie metody na obiekcie generatora (np. f
Jak w poniższym przykładzie) zwrócone przez funkcję generatora (na przykład: foo()
funkcja w poniższym przykładzie), generuje kolejną wartość w sekwencji.
Wywołanie funkcji generatora powoduje zwrócenie obiektu generatora nawet bez rozpoczęcia wykonywania funkcji. Gdy next()
metoda jest wywoływana po raz pierwszy, funkcja rozpoczyna wykonywanie, dopóki nie osiągnie instrukcji dochodu, która zwraca uzyskaną wartość. Wydajność śledzi np. Pamięta ostatnie wykonanie. Drugie next()
połączenie jest kontynuowane od poprzedniej wartości.
Poniższy przykład pokazuje wzajemne oddziaływanie między wydajnością a wywołaniem kolejnej metody na obiekcie generatora.
>>> def foo():
... print "begin"
... for i in range(3):
... print "before yield", i
... yield i
... print "after yield", i
... print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0 # Control is in for loop
0
>>> f.next()
after yield 0
before yield 1 # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Dodanie odpowiedzi, ponieważ żadna z istniejących odpowiedzi nie odnosi się konkretnie do zamieszania w oficjalnej literaturze.
Funkcje generatora to zwykłe funkcje zdefiniowane za pomocąyield
zamiastreturn
. Po wywołaniu funkcja generatora zwraca obiekt generatora , który jest rodzajem iteratora - manext()
metodę. Po wywołaniunext()
zwracana jest następna wartość uzyskana przez funkcję generatora.
Zarówno funkcja, jak i obiekt mogą być nazywane „generatorem” w zależności od tego, który dokument źródłowy w Pythonie czytasz. Python słowniczek mówi funkcji generatora, podczas gdy Python wiki implikuje obiekty generatora. Samouczek Python wybitnie zarządza się sugerować zarówno widma w przestrzeni trzech zdaniach:
Generatory to proste i wydajne narzędzie do tworzenia iteratorów. Są one pisane jak zwykłe funkcje, ale używają instrukcji fed za każdym razem, gdy chcą zwrócić dane. Za każdym razem, gdy wywoływana jest funkcja next (), generator wznawia od miejsca, w którym został przerwany (zapamiętuje wszystkie wartości danych i ostatnią instrukcję).
Pierwsze dwa zdania identyfikują generatory z funkcjami generatora, a trzecie zdanie identyfikuje je z obiektami generatora.
Pomimo całego tego zamieszania, można znaleźć odniesienie do języka Python dla jasnego i końcowego słowa:
Wyrażenie wydajności jest używane tylko podczas definiowania funkcji generatora i może być użyte tylko w treści definicji funkcji. Użycie wyrażenia plonu w definicji funkcji jest wystarczające, aby definicja ta utworzyła funkcję generatora zamiast funkcji normalnej.
Po wywołaniu funkcji generatora zwraca iterator zwany generatorem. Ten generator kontroluje następnie wykonywanie funkcji generatora.
Tak więc, w formalnym i precyzyjnym użyciu, „generator” bez zastrzeżeń oznacza obiekt generatora, a nie funkcję generatora.
Powyższe odniesienia dotyczą Python 2, ale odniesienie do języka Python 3 mówi to samo. Jednak glosariusz Python 3 stwierdza, że
generator ... Zwykle odnosi się do funkcji generatora, ale może odnosić się do iteratora generatora w niektórych kontekstach. W przypadkach, gdy zamierzone znaczenie nie jest jasne, użycie pełnych terminów pozwala uniknąć dwuznaczności.
Każdy ma naprawdę miłą i pełną odpowiedzi odpowiedź z przykładami i bardzo to doceniam. Chciałem tylko udzielić krótkiej linijki odpowiedzi dla osób, które wciąż nie są całkiem jasne koncepcyjnie:
Jeśli utworzysz własny iterator, jest to trochę zaangażowane - musisz stworzyć klasę i przynajmniej zaimplementować iter i kolejne metody. Ale co, jeśli nie chcesz przejść przez ten problem i chcesz szybko utworzyć iterator. Na szczęście Python zapewnia skrót do definiowania iteratora. Wszystko, co musisz zrobić, to zdefiniować funkcję z co najmniej 1 wywołaniem, aby uzyskać, a teraz, gdy wywołasz tę funkcję, zwróci „ coś ”, co będzie działać jak iterator (możesz wywołać następną metodę i użyć jej w pętli for). To coś ma w Pythonie nazwę Generator
Mam nadzieję, że to trochę wyjaśnia.
Poprzednie odpowiedzi pomijały ten dodatek: generator ma close
metodę, podczas gdy typowe iteratory nie. Ta close
metoda wyzwala StopIteration
wyjątek w generatorze, który może zostać przechwycony w finally
klauzuli w tym iteratorze, aby uzyskać szansę na przeprowadzenie czyszczenia. Ta abstrakcja sprawia, że jest najbardziej użyteczna w dużych niż prostych iteratorach. Generator można zamknąć tak, jak można zamknąć plik, bez zawracania sobie głowy tym, co jest pod spodem.
To powiedziawszy, moja osobista odpowiedź na pierwsze pytanie brzmiałaby: iterowalna ma __iter__
tylko metodę, typowe iteratory mają __next__
tylko metodę, generatory mają zarówno __iter__
i, jak __next__
i dodatkową close
.
W przypadku drugiego pytania moja osobista odpowiedź brzmiałaby: w publicznym interfejsie mam tendencję do faworyzowania generatorów, ponieważ jest on bardziej odporny: close
metoda ma większą kompozycję yield from
. Lokalnie mogę używać iteratorów, ale tylko wtedy, gdy jest to płaska i prosta struktura (iteratory nie dają się łatwo komponować) i jeśli istnieją powody, by sądzić, że sekwencja jest raczej krótka, szczególnie jeśli można ją zatrzymać przed osiągnięciem końca. Zwykle patrzę na iteratory jako na prymitywny poziom niski, z wyjątkiem literałów.
W kwestiach związanych z przepływem sterowania generatory są równie ważną koncepcją, jak obietnice: oba są abstrakcyjne i można je komponować.
__iter__
metodę, to dlaczego iterator może mieć __next__
tylko? Gdyby miały to być iterowalne, oczekiwałbym, że również będą musiały __iter__
.
__iter__
iterabeli do zwrócenia iteratora, który wymaga tylko next
metody ( __next__
w Python3). Proszę nie mylić standardów (dotyczących pisania kaczego) z ich implementacją (jak zaimplementował to konkretny interpreter Pythona). To trochę przypomina pomieszanie funkcji generatora (definicja) i obiektów generatora (implementacja). ;)
Funkcja generatora, obiekt generatora, generator:
Funkcja Generatora jest jak zwykła funkcja w Pythonie, ale zawiera jedną lub więcej yield
instrukcji. Funkcje generatora to świetne narzędzie do tworzenia obiektów Iterator tak łatwo, jak to możliwe. Iterator obiekt returend przez funkcję generatora jest również nazywany obiektem Generator lub generatora .
W tym przykładzie utworzyłem funkcję Generator, która zwraca obiekt Generator <generator object fib at 0x01342480>
. Podobnie jak inne iteratory, obiekty Generatora mogą być używane w for
pętli lub z wbudowaną funkcją, next()
która zwraca następną wartość z generatora.
def fib(max):
a, b = 0, 1
for i in range(max):
yield a
a, b = b, a + b
print(fib(10)) #<generator object fib at 0x01342480>
for i in fib(10):
print(i) # 0 1 1 2 3 5 8 13 21 34
print(next(myfib)) #0
print(next(myfib)) #1
print(next(myfib)) #1
print(next(myfib)) #2
Zatem funkcja generatora jest najprostszym sposobem na utworzenie obiektu Iterator.
Iterator :
Każdy obiekt generatora jest iteratorem, ale nie odwrotnie. Niestandardowy obiekt iteratora można utworzyć, jeśli jego klasa implementuje metodę __iter__
i __next__
metodę (zwaną także protokołem iteratora).
Jednak o wiele łatwiej jest używać funkcji generatorów do tworzenia iteratorów, ponieważ upraszczają one ich tworzenie, ale niestandardowy Iterator daje większą swobodę i możesz także implementować inne metody zgodnie z własnymi wymaganiami, jak pokazano w poniższym przykładzie.
class Fib:
def __init__(self,max):
self.current=0
self.next=1
self.max=max
self.count=0
def __iter__(self):
return self
def __next__(self):
if self.count>self.max:
raise StopIteration
else:
self.current,self.next=self.next,(self.current+self.next)
self.count+=1
return self.next-self.current
def __str__(self):
return "Generator object"
itobj=Fib(4)
print(itobj) #Generator object
for i in Fib(4):
print(i) #0 1 1 2
print(next(itobj)) #0
print(next(itobj)) #1
print(next(itobj)) #1
Przykłady z Ned Batchelder wysoce zalecane dla iteratorów i generatorów
Metoda bez generatorów, które robią coś z liczbami parzystymi
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
podczas korzystania z generatora
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
oświadczeniaWywołanie evens
metody (generator) jest jak zwykle
num = [...]
for n in evens(num):
do_smth(n)
Iterator
Książka pełna stron jest iterable , zakładka jest iterator
i ta zakładka nie ma nic innego, jak się przenieść next
litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration (Exception) as we got end of the iterator
Aby użyć Generatora ... potrzebujemy funkcji
Aby użyć Iteratora ... potrzebujemy next
iiter
Jak powiedziano:
Funkcja Generator zwraca obiekt iteratora
Cała zaleta Iteratora:
Przechowuj jeden element na raz w pamięci
Możesz porównać oba podejścia dla tych samych danych:
def myGeneratorList(n):
for i in range(n):
yield i
def myIterableList(n):
ll = n*[None]
for i in range(n):
ll[i] = i
return ll
# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
print("{} {}".format(i1, i2))
# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
Poza tym, jeśli sprawdzisz powierzchnię pamięci, generator zajmie znacznie mniej pamięci, ponieważ nie musi jednocześnie przechowywać wszystkich wartości w pamięci.
Piszę specjalnie dla początkujących w Pythonie w bardzo prosty sposób, choć w głębi duszy Python robi tak wiele rzeczy.
Zacznijmy od bardzo podstawowego:
Rozważ listę,
l = [1,2,3]
Napiszmy równoważną funkcję:
def f():
return [1,2,3]
o / p z print(l): [1,2,3]
& o / p zprint(f()) : [1,2,3]
Zróbmy listę l iterowalną: Na liście python jest zawsze iterowalna, co oznacza, że możesz zastosować iterator, kiedy chcesz.
Zastosujmy iterator na liście:
iter_l = iter(l) # iterator applied explicitly
Stwórzmy iterowalną funkcję, tj. Napisz równoważną funkcję generatora.
W python, jak tylko wprowadzisz słowo kluczowe yield
; staje się funkcją generatora i iterator zostanie zastosowany niejawnie.
Uwaga: Każdy generator jest zawsze iterowalny z zastosowanym niejawnym iteratorem, a tutaj ukryty iterator jest najważniejszy. Więc funkcja generatora będzie:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Więc jeśli zauważyłeś, jak tylko utworzyłeś funkcję generatora fa, jest już iteracyjny (f)
Teraz,
l jest listą, po zastosowaniu metody iteracyjnej „iter” staje się iter (l)
f jest już iter (f), po zastosowaniu metody iteratora „iter” staje się iter (iter (f)), który ponownie jest iter (f)
Trochę rzucasz int na int (x), który już jest int i pozostanie int (x).
Na przykład o / p:
print(type(iter(iter(l))))
jest
<class 'list_iterator'>
Nigdy nie zapominaj, że jest to Python, a nie C lub C ++
Stąd wniosek z powyższego wyjaśnienia jest następujący:
lista l ~ = iter (l)
funkcja generatora f == iter (f)