Різниця між генераторами і ітераторами Python


537

Чим відрізняються ітератори від генераторів? Деякі приклади, коли ви використовували б кожен випадок, були б корисні.

Відповіді:


542

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

якщо у вас є реальна потреба у такій додатковій функціональності у вашій програмі.


як я можу використовувати ітератор, як тільки я його створив?
Vincenzooo

@Vincenzooo, це залежить від того, що ви хочете зробити з цим. Це буде або частина частини for ... in ...:, переданої функції, або дзвонитьiter.next()
Caleth

@Caleth я запитував про точний синтаксис, тому що я отримував помилку при спробі використання for..inсинтаксису. Можливо, мені щось не вистачало, але це було певний час тому, я не пам'ятаю, якби вирішив. Дякую!
Vincenzooo

135

Чим відрізняються ітератори від генераторів? Деякі приклади, коли ви використовували б кожен випадок, були б корисні.

Підсумовуючи це: Ітератори - це об'єкти, які мають метод __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твердженням, глибоко про свою відповідь на те, "Що робить ключове слово" вихід "?".


41

Ітератори:

Ітератор - це об'єкти, які використовує 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
>>>

3
Просто вихід ПІІ - це не метод, це ключове слово
Джей Парих

25

Додавання відповіді, оскільки жодна з існуючих відповідей конкретно не стосується плутанини в офіційній літературі.

Функції генератора - це звичайні функції, визначені за допомогою,yieldа неreturn. При виклику функція генератора повертає об'єкт генератора , який є своєрідним ітератором - у нього єnext()метод. Коли ви телефонуєтеnext(), повертається наступне значення, отримане функцією генератора.

Або функцію, або об'єкт можна назвати "генератором" залежно від того, який вихідний документ Python ви читаєте. У словнику Python вказується функція генератора, тоді як вікі Python передбачає генераторні об'єкти. Навчальний посібник Python чудово вдається передбачити обидва звички в просторі трьох речень:

Генератори - простий і потужний інструмент для створення ітераторів. Вони записуються як звичайні функції, але використовують заявку про вихід, коли вони хочуть повернути дані. Кожного разу, коли next () викликається на ньому, генератор поновлюється там, де він вийшов з ладу (він запам'ятовує всі значення даних і те, що оператор востаннє виконувався).

Перші два речення ототожнюють генератори з функціями генератора, а третє речення ототожнює їх з об'єктами генератора.

Незважаючи на всю цю плутанину, можна знайти посилання на мову Python для чіткого та остаточного слова:

Вираз виходу використовується лише при визначенні функції генератора і може використовуватися лише в тілі визначення функції. Використання виразу урожайності у визначенні функції достатньо для того, щоб це визначення створило функцію генератора замість нормальної функції.

Коли викликається функція генератора, він повертає ітератор, відомий як генератор. Потім цей генератор контролює виконання функції генератора.

Отже, в формальному та точному використанні "генератор" без кваліфікації означає об'єкт генератора, а не функцію генератора.

Наведені вище посилання стосуються Python 2, але посилання на мову Python 3 говорить те саме. Однак глосарій Python 3 стверджує, що

генератор ... Зазвичай відноситься до функції генератора, але може стосуватися ітератора генератора в деяких контекстах. У випадках, коли задумане значення не зрозуміло, використання повних термінів дозволяє уникнути неоднозначності.


Я не думаю, що між функціями генератора та об'єктами генератора існує велика плутанина, з тієї ж причини зазвичай немає плутанини між класами та їх екземплярами. В обох випадках ви телефонуєте одному, щоб отримати іншого, і в випадковій розмові (або швидко написаній документації) ви можете використовувати назву класу або слово "генератор" для будь-якого. Вам потрібно бути чітким щодо "функції генератора" проти "об'єкта генератора" в рідкісних ситуаціях, коли про який ви говорите.
Blckknght

6
1. Незалежно від теоретичних причин, чому не повинно виникати плутанини, коментарі інших відповідей на це питання заперечують і суперечать один одному без вирішення, вказуючи на те, що існує фактична плутанина. 2. Випадкова неточність є чудовою, але точне, авторитетне джерело повинно бути, як мінімум, одним із варіантів використання програмного забезпечення. Я широко використовую як функції генератора, так і об'єкти в своєму поточному проекті, і відмінність дуже важлива при проектуванні та кодуванні. Добре знати, якою термінологією зараз користуватися, тому мені не доведеться згодом міняти десятки змінних імен та коментарів.
Павло

2
Уявіть математичну літературу, де не робиться різниці між функцією та її зворотним значенням. Іноді зручно їх пов'язувати неформально, але це збільшує ризик різноманітних помилок. Сучасна сучасна математика була б суттєво і непотрібно заважати, якби відмінність не була формалізована у конвенції, мові та позначеннях.
Павло

2
Функції вищого порядку, що проходять навколо генераторів або функцій генератора, можуть здатися дивними, але для мене вони з'являються. Я працюю в Apache Spark, і це застосовує дуже функціональний стиль програмування. Функції повинні створювати, передавати і передавати всілякі об'єкти, щоб зробити це можливим. У мене виникла низка ситуацій, коли я втрачав інформацію про те, з яким «генератором» я працював. Підказки щодо змінних імен та коментарів, використовуючи послідовну та правильну термінологію, допомогли усунути плутанину. Невідомість одного Pythonist може бути центром дизайну іншого!
Павло

1
@Paul, дякую за написання цієї відповіді. Ця плутанина важлива, оскільки різниця між об'єктом генератора та функцією генератора - це різниця між отриманням бажаної поведінки та необхідністю пошуку генераторів.
blujay

16

У всіх є дуже приємна і багатослівна відповідь із прикладами, і я дуже ціную це. Я просто хотів дати кілька коротких рядків відповіді для людей, які ще не зовсім зрозумілі концептуально:

Якщо ви створюєте власний ітератор, це трохи задіяно - вам слід створити клас і принаймні реалізувати ітератор та наступні методи. Але що робити, якщо ви не хочете пройти цю проблему і хочете швидко створити ітератор. На щастя, Python пропонує короткий шлях визначення ітератора. Все, що вам потрібно зробити, - це визначити функцію щонайменше з 1 викликом для виходу, і тепер, коли ви зателефонуєте на цю функцію, вона поверне " щось ", яке буде діяти як ітератор (ви можете зателефонувати наступним методом і використовувати його в циклі for). Це щось має ім’я в Python під назвою Generator

Сподіваюсь, що трохи уточнює.


9

Попередні відповіді пропустили це доповнення: генератор має closeметод, а типові ітератори - ні. closeМетод викликає StopIterationвиключення в генераторі, який може бути спійманий вfinally статті в цьому ітератора, щоб отримати можливість запускати деякі очищення. Ця абстракція робить її найбільш корисною для великих, ніж простих ітераторів. Генератор може закрити так, як можна було б закрити файл, не турбуючись про те, що знаходиться під ним.

Отож, моя особиста відповідь на перше запитання була б така: ітерабельний __iter__метод має лише метод, типові ітератори - __next__лише метод, генератори мають і __iter__і, __next__і додатковеclose .

На друге запитання моя особиста відповідь була б така: в загальнодоступному інтерфейсі я, як правило, віддаю перевагу генераторам, оскільки це більш стійкий: closeметод - більша сумісністьyield from . Локально я можу використовувати ітератори, але лише якщо це плоска і проста структура (ітератори не складаються легко) і якщо є підстави вважати, що послідовність є досить короткою, особливо якщо вона може бути зупинена до її завершення. Я схильний дивитися на ітераторів як на примітив низького рівня, за винятком буквальних.

Для питань управління потоками генератори - це настільки ж важливе поняття, як і обіцянки: обидва є абстрактними та композиційними.


Чи можете ви навести приклад, щоб проілюструвати, що ви хочете сказати про композицію? Також, чи можете ви пояснити, що ви маєте на увазі, коли говорити про " типових ітераторів"?
блі

1
Інша відповідь ( stackoverflow.com/a/28353158/1878788 ) стверджує, що "ітератор є ітерабельним". Оскільки в ітерабелі є __iter__метод, то як ітератор може мати __next__тільки? Якщо вони повинні бути ітерабельними, я б очікував, що вони __iter__теж матимуть це.
блі

1
@bli: AFAICS ця відповідь тут відноситься до стандарту PEP234 , тому вона правильна, тоді як інша відповідь стосується деякої реалізації, тому вона сумнівна. Стандарт вимагає лише __iter__ітерабелів повернути ітератор, який вимагає лише nextметоду ( __next__в Python3). Будь ласка, не плутайте стандарти (для набору качок) з їх реалізацією (як конкретний інтерпретатор Python реалізував це). Це трохи схоже на плутанину між функціями генератора (визначення) та об'єктами генератора (реалізація). ;)
Тіно

7

Функція генератора, Об'єкт генератора, Генератор:

Функція 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

6

Приклади Неда Батчелдера дуже рекомендуються для ітераторів та генераторів

Метод без генераторів, які щось роблять для парних чисел

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 повертає об'єкт ітератора

Вся користь Ітератора:

Зберігайте один елемент у пам’яті


Щодо вашого першого фрагмента коду, я хотів би знати, що ще може бути аргумент «stream», ніж список []?
Ікра.

5

Ви можете порівняти обидва підходи для одних і тих же даних:

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)))

Крім того, якщо ви перевіряєте слід пам'яті, генератор займає набагато менше пам’яті, оскільки йому не потрібно одночасно зберігати всі значення в пам'яті.


1

Я пишу спеціально для новачків 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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.