Чим відрізняються ітератори від генераторів? Деякі приклади, коли ви використовували б кожен випадок, були б корисні.
Чим відрізняються ітератори від генераторів? Деякі приклади, коли ви використовували б кожен випадок, були б корисні.
Відповіді:
iterator
є більш загальним поняттям: будь-який об'єкт, клас якого має next
метод ( __next__
в Python 3) і __iter__
метод, який це робить return self
.
Кожен генератор є ітератором, але не навпаки. Генератор будується за допомогою виклику функції, яка має одне або більше yield
виразів ( yield
заяви, в Python 2.5 та новіших версіях), і є об'єктом, що відповідає визначенню попереднього абзацуiterator
.
Якщо вам потрібен клас із дещо складною поведінкою, що підтримує стан, або ви хочете розкрити інші методи, окрім next
(і __iter__
та __init__
), ви можете скористатися користувацьким ітератором, а не генератором . Найчастіше генератора (іноді для досить простих потреб, вираження генератора ) достатньо, і його простіше кодувати, оскільки технічне обслуговування держави (в розумних межах) в основному "робиться за вас", коли кадр припиняється і поновлюється.
Наприклад, такий генератор, як:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
або еквівалентний вираз генератора (genexp)
generator = (i*i for i in range(a, b))
знадобиться більше коду для складання як спеціальний ітератор:
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)
Але, звичайно, з класом Squares
ви могли легко запропонувати додаткові методи, тобто
def current(self):
return self.start
якщо у вас є реальна потреба у такій додатковій функціональності у вашій програмі.
for ... in ...:
, переданої функції, або дзвонитьiter.next()
for..in
синтаксису. Можливо, мені щось не вистачало, але це було певний час тому, я не пам'ятаю, якби вирішив. Дякую!
Чим відрізняються ітератори від генераторів? Деякі приклади, коли ви використовували б кожен випадок, були б корисні.
Підсумовуючи це: Ітератори - це об'єкти, які мають метод __iter__
a і __next__
( next
у Python 2). Генератори забезпечують простий, вбудований спосіб створення примірників Ітераторів.
Функція з виходом у ній все-таки є функцією, яка при виклику повертає екземпляр об'єкта-генератора:
def a_function():
"when called, returns generator object"
yield
Вираз генератора також повертає генератор:
a_generator = (i for i in range(0))
Щоб отримати більш поглиблену експозицію та приклади, читайте.
Зокрема, генератор є підтипом ітератора.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Ми можемо створити генератор декількома способами. Дуже поширений і простий спосіб зробити це за допомогою функції.
Зокрема, функція з виходом у ній - це функція, яка при виклику повертає генератор:
>>> 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'>
І генератор, знову ж таки, є ітератором:
>>> isinstance(a_generator, collections.Iterator)
True
Ітератор - це Ітерабельний,
>>> issubclass(collections.Iterator, collections.Iterable)
True
який вимагає __iter__
методу, який повертає ітератор:
>>> 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__
Деякі приклади ітерабелів - це вбудовані кортежі, списки, словники, набори, заморожені набори, рядки, рядки байтів, масиви байтів, діапазони та перегляди пам'яті:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
або__next__
методуУ 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
А в 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__
Ми можемо отримати ітератори з вбудованих об'єктів (або спеціальних об'єктів) з iter
функцією:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
__iter__
Метод викликається при спробі використовувати об'єкт з для петлі. Потім __next__
метод викликається на об'єкт ітератора, щоб отримати кожен елемент для циклу. Ітератор піднімається, StopIteration
коли ви його вичерпали, і його неможливо повторно використати.
З розділі Типи генератора в розділі Типи Итератор Вбудовані типи документації :
Генератори Python забезпечують зручний спосіб реалізації протоколу ітератора. Якщо
__iter__()
метод об'єкта контейнера реалізований як генератор, він автоматично поверне ітераторний об'єкт (технічно - об’єкт генератора), що постачає методи [__iter__()
і в Python 3]. Більше інформації про генератори можна знайти в документації для виразу виходу.next()
__next__()
(Наголос додано.)
Отже, з цього ми дізнаємось, що Генератори - це (зручний) тип Ітератора.
Ви можете створити об’єкт, який реалізує протокол Iterator, створивши або розширивши власний об’єкт.
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
Але для цього простіше просто використовувати Генератор:
def yes(stop):
for _ in range(stop):
yield 'yes'
Або, мабуть, простіше, Генератор Вираз (працює аналогічно списку розумінь):
yes_expr = ('yes' for _ in range(stop))
Усі вони можуть використовуватися однаково:
>>> 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
Ви можете використовувати протокол Iterator безпосередньо, коли вам потрібно розширити об'єкт Python як об'єкт, над яким можна повторити.
Однак у переважній більшості випадків ви найкраще використовувати yield
для визначення функції, яка повертає ітератор генератора або розглядає генераторні вирази.
Нарешті, зауважте, що генератори забезпечують ще більшу функціональність, як підпрограми. Я пояснюю Генераторам, разом із yield
твердженням, глибоко про свою відповідь на те, "Що робить ключове слово" вихід "?".
Ітератори:
Ітератор - це об'єкти, які використовує next()
метод для отримання наступного значення послідовності.
Генератори:
Генератор - це функція, яка виробляє або дає послідовність значень, використовуючи yield
методу.
Кожен next()
виклик методу на об'єкт генератора (наприклад, f
як у наведеному нижче прикладі), що повертається функцією генератора (наприклад, foo()
функція у наведеному нижче прикладі), генерує наступне значення в послідовності.
Коли викликається функція генератора, він повертає об'єкт генератора, навіть не починаючи виконувати функцію. Коли next()
метод викликається вперше, функція починає виконуватись до тих пір, поки не досягне оператора виходу, який повертає вихідне значення. Врожайність відстежує, тобто запам'ятовує останнє виконання. А другий next()
дзвінок продовжується з попереднього значення.
Наступний приклад демонструє взаємодію між урожайністю та викликом до наступного методу на генераторному об'єкті.
>>> 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
>>>
Додавання відповіді, оскільки жодна з існуючих відповідей конкретно не стосується плутанини в офіційній літературі.
Функції генератора - це звичайні функції, визначені за допомогою,yield
а неreturn
. При виклику функція генератора повертає об'єкт генератора , який є своєрідним ітератором - у нього єnext()
метод. Коли ви телефонуєтеnext()
, повертається наступне значення, отримане функцією генератора.
Або функцію, або об'єкт можна назвати "генератором" залежно від того, який вихідний документ Python ви читаєте. У словнику Python вказується функція генератора, тоді як вікі Python передбачає генераторні об'єкти. Навчальний посібник Python чудово вдається передбачити обидва звички в просторі трьох речень:
Генератори - простий і потужний інструмент для створення ітераторів. Вони записуються як звичайні функції, але використовують заявку про вихід, коли вони хочуть повернути дані. Кожного разу, коли next () викликається на ньому, генератор поновлюється там, де він вийшов з ладу (він запам'ятовує всі значення даних і те, що оператор востаннє виконувався).
Перші два речення ототожнюють генератори з функціями генератора, а третє речення ототожнює їх з об'єктами генератора.
Незважаючи на всю цю плутанину, можна знайти посилання на мову Python для чіткого та остаточного слова:
Вираз виходу використовується лише при визначенні функції генератора і може використовуватися лише в тілі визначення функції. Використання виразу урожайності у визначенні функції достатньо для того, щоб це визначення створило функцію генератора замість нормальної функції.
Коли викликається функція генератора, він повертає ітератор, відомий як генератор. Потім цей генератор контролює виконання функції генератора.
Отже, в формальному та точному використанні "генератор" без кваліфікації означає об'єкт генератора, а не функцію генератора.
Наведені вище посилання стосуються Python 2, але посилання на мову Python 3 говорить те саме. Однак глосарій Python 3 стверджує, що
генератор ... Зазвичай відноситься до функції генератора, але може стосуватися ітератора генератора в деяких контекстах. У випадках, коли задумане значення не зрозуміло, використання повних термінів дозволяє уникнути неоднозначності.
У всіх є дуже приємна і багатослівна відповідь із прикладами, і я дуже ціную це. Я просто хотів дати кілька коротких рядків відповіді для людей, які ще не зовсім зрозумілі концептуально:
Якщо ви створюєте власний ітератор, це трохи задіяно - вам слід створити клас і принаймні реалізувати ітератор та наступні методи. Але що робити, якщо ви не хочете пройти цю проблему і хочете швидко створити ітератор. На щастя, Python пропонує короткий шлях визначення ітератора. Все, що вам потрібно зробити, - це визначити функцію щонайменше з 1 викликом для виходу, і тепер, коли ви зателефонуєте на цю функцію, вона поверне " щось ", яке буде діяти як ітератор (ви можете зателефонувати наступним методом і використовувати його в циклі for). Це щось має ім’я в Python під назвою Generator
Сподіваюсь, що трохи уточнює.
Попередні відповіді пропустили це доповнення: генератор має close
метод, а типові ітератори - ні. close
Метод викликає StopIteration
виключення в генераторі, який може бути спійманий вfinally
статті в цьому ітератора, щоб отримати можливість запускати деякі очищення. Ця абстракція робить її найбільш корисною для великих, ніж простих ітераторів. Генератор може закрити так, як можна було б закрити файл, не турбуючись про те, що знаходиться під ним.
Отож, моя особиста відповідь на перше запитання була б така: ітерабельний __iter__
метод має лише метод, типові ітератори - __next__
лише метод, генератори мають і __iter__
і, __next__
і додатковеclose
.
На друге запитання моя особиста відповідь була б така: в загальнодоступному інтерфейсі я, як правило, віддаю перевагу генераторам, оскільки це більш стійкий: close
метод - більша сумісністьyield from
. Локально я можу використовувати ітератори, але лише якщо це плоска і проста структура (ітератори не складаються легко) і якщо є підстави вважати, що послідовність є досить короткою, особливо якщо вона може бути зупинена до її завершення. Я схильний дивитися на ітераторів як на примітив низького рівня, за винятком буквальних.
Для питань управління потоками генератори - це настільки ж важливе поняття, як і обіцянки: обидва є абстрактними та композиційними.
__iter__
метод, то як ітератор може мати __next__
тільки? Якщо вони повинні бути ітерабельними, я б очікував, що вони __iter__
теж матимуть це.
__iter__
ітерабелів повернути ітератор, який вимагає лише next
методу ( __next__
в Python3). Будь ласка, не плутайте стандарти (для набору качок) з їх реалізацією (як конкретний інтерпретатор Python реалізував це). Це трохи схоже на плутанину між функціями генератора (визначення) та об'єктами генератора (реалізація). ;)
Функція генератора, Об'єкт генератора, Генератор:
Функція Generator подібна до звичайної функції в Python, але вона містить одне або більше yield
операторів. Функції генератора - чудовий інструмент для максимально простого створення об'єктів Ітератора . Ітератора об'єкт returend з допомогою функції генератора також називається об'єктом генератора або генератора .
У цьому прикладі я створив функцію Generator, яка повертає об'єкт Generator <generator object fib at 0x01342480>
. Як і інші ітератори, об'єкти Generator можна використовувати в for
циклі або за допомогою вбудованої функції, next()
яка повертає наступне значення від генератора.
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
Отже, функція генератора - це найпростіший спосіб створити об'єкт Ітератор.
Ітератор :
Кожен об’єкт генератора є ітератором, але не навпаки. Спеціальний об'єкт ітератора може бути створений, якщо його клас реалізує __iter__
та __next__
метод (також його називають протоколом ітератора).
Однак набагато простіше використовувати генератори функції для створення ітераторів, оскільки вони спрощують їх створення, але користувацький ітератор дає вам більше свободи, і ви також можете реалізувати інші методи відповідно до ваших вимог, як показано в наведеному нижче прикладі.
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
Приклади Неда Батчелдера дуже рекомендуються для ітераторів та генераторів
Метод без генераторів, які щось роблять для парних чисел
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
при використанні генератора
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
заяваВиклик evens
методу (генератора) відбувається як завжди
num = [...]
for n in evens(num):
do_smth(n)
Ітератор
Книга, повна сторінок, - це ітератор , закладка - ітератор
і ця закладка не має нічого спільного, ніж рухатися 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
Для використання Generator ... нам потрібна функція
Для використання Iterator ... нам потрібно next
іiter
Як було сказано:
Функція Generator повертає об'єкт ітератора
Вся користь Ітератора:
Зберігайте один елемент у пам’яті
Ви можете порівняти обидва підходи для одних і тих же даних:
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)))
Крім того, якщо ви перевіряєте слід пам'яті, генератор займає набагато менше пам’яті, оскільки йому не потрібно одночасно зберігати всі значення в пам'яті.
Я пишу спеціально для новачків Python дуже простим способом, хоча в глибокій глибині Python робить так багато речей.
Почнемо з самого основного:
Розгляньте список,
l = [1,2,3]
Напишемо еквівалентну функцію:
def f():
return [1,2,3]
o / p of print(l): [1,2,3]
& o / p ofprint(f()) : [1,2,3]
Давайте зробимо список l ітерабельним: У списку python завжди можна перетворити, це означає, що ви можете застосовувати ітератор будь-коли.
Давайте застосуємо ітератор у списку:
iter_l = iter(l) # iterator applied explicitly
Давайте зробимо функцію ітерабельною, тобто запишемо еквівалентну функцію генератора.
У python, як тільки ви введете ключове слово yield
; він стає функцією генератора, ітератор буде застосовано неявно.
Примітка: Кожен генератор завжди ітерабельний із застосованим неявним ітератором, і тут імпліцит неявного ітератора є суттєвим, тому функція генератора буде:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Отже, якщо ви помітили, щойно ви зробили генератор функцій fa, це вже iter (f)
Тепер,
l - це список, після застосування методу ітератора "iter" він стає, iter (l)
f - це вже iter (f), після застосування методу ітератора "iter" він стає iter (iter (f)), який знову є iter (f)
Це як би ви кидали int в int (x), який вже є int, і він залишиться int (x).
Наприклад o / p:
print(type(iter(iter(l))))
є
<class 'list_iterator'>
Ніколи не забувайте, що це Python, а не C або C ++
Звідси висновок із наведеного пояснення:
список l ~ = iter (l)
функція генератора f == iter (f)