Python的生成器和迭代器之间的区别

迭代器和生成器有什么区别?一些关于何时使用每个案例的示例会很有帮助。

231617 次浏览

iterator是一个更一般的概念:其类具有__next__方法(Python 2中的next)和__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 2
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

如果您在应用程序中实际需要此类额外功能。

迭代器是使用next()方法获取序列的以下值的对象。

发电机是使用yield关键字生成或产生值序列的函数。

生成器函数(对于下面的ex:foo())返回的生成器对象(对于下面的ex:f)上的每个next()方法调用都会生成序列中的下一个值。

当调用生成器函数时,它返回一个生成器对象,甚至不开始执行该函数。当第一次调用next()方法时,该函数开始执行,直到它到达返回产生值的yield语句。yield跟踪发生了什么,即它记住最后一次执行。其次,next()调用从上一个值继续。

以下示例演示了yield和对生成器对象上的next方法的调用之间的相互作用。

>>> def foo():
...     print("begin")
...     for i in range(3):
...         print("before yield", i)
...         yield i
...         print("after yield", i)
...     print("end")
...
>>> f = foo()
>>> next(f)
begin
before yield 0            # Control is in for loop
0
>>> next(f)
after yield 0
before yield 1            # Continue for loop
1
>>> next(f)
after yield 1
before yield 2
2
>>> next(f)
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

迭代器和生成器有什么区别?一些关于何时使用每个案例的示例会很有帮助。

总结:迭代器是具有__iter____next__(Python 2中的next)方法的对象。生成器提供了一种简单的内置方式来创建迭代器实例。

一个函数,如果它包含了产量,它仍然是一个函数,当它被调用时,它会返回一个生成器对象的实例:

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

一个迭代器一个可迭代的

Iterator是一个可迭代的,

>>> issubclass(collections.Iterator, collections.Iterable)
True

这需要一个返回Iterator的__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

迭代器要求 anext__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

当你尝试使用带有for循环的对象时,会调用__iter__方法。然后在迭代器对象上调用__next__方法以将每个项目取出循环。迭代器在你耗尽它时引发StopIteration,此时它无法重用。

从留档

从内置类型留档的迭代器类型部分的生成器类型部分:

Python的生成器提供了一种方便的方式来实现迭代器协议。如果容器对象的__iter__()方法被实现为生成器,它将自动返回一个迭代器对象(技术上是生成器对象),提供__iter__()next()[__next__() in Python 3]方法。有关生成器的更多信息,请参阅产量表达式的留档。

(强调添加。

因此,从这里我们了解到生成器是一种(方便的)迭代器类型。

示例迭代器对象

您可以通过创建或扩展自己的对象来创建实现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

结论

当您需要将Python对象扩展为可以迭代的对象时,您可以直接使用Iterator协议。

但是,在绝大多数情况下,您最适合使用yield来定义返回生成器迭代器的函数或考虑生成器表达式。

最后,请注意生成器作为协程提供了更多的功能。我解释了生成器,随着yield语句,深入我的回答“什么是”产量“关键字做什么?”。

增加一个答案,因为现有的答案都没有具体解决官方文献中的混乱。

生成器函数是使用yield而不是return定义的普通函数。调用时,生成器函数返回生成器对象,这是一种迭代器-它有一个next()方法。当你调用next()时,将返回生成器函数产生的下一个值。

函数或对象都可以称为“生成器”,这取决于您阅读的Python源文档。python词汇表表示生成器函数,而python wiki暗示生成器对象。python教程显着地设法在三个句子的空间中暗示两者的用法:

生成器是用于创建迭代器的简单而强大的工具。它们像常规函数一样编写,但在想要返回数据时使用产量语句。每次调用next()时,生成器都会从它停止的地方恢复(它会记住所有数据值以及上次执行的语句)。

前两句用生成器函数标识生成器,第三句用生成器对象标识生成器。

尽管存在所有这些混乱,但人们可以寻找python语言参考来获得清晰和最终的单词:

产量表达式仅在定义生成器函数时使用,并且只能在函数定义的主体中使用。在函数定义中使用产量表达式足以导致该定义创建生成器函数而不是普通函数。

调用生成器函数时,它返回一个称为生成器的迭代器。然后,该生成器控制生成器函数的执行。

因此,在正式和精确的使用中,“生成器”不合格表示生成器对象,而不是生成器函数。

上面的参考是针对Python 2的,但Python 3语言参考说了同样的事情。然而,Python 3词汇表指出

通常指的是生成器函数,但在某些上下文中可能指的是生成器迭代器。在寓意不清楚的情况下,使用完整术语可以避免歧义。

生成器函数、生成器对象、生成器:

生成器函数就像Python中的常规函数一样,但它包含一个或多个yield语句。生成器函数是尽可能简单地创建迭代器对象的好工具。生成器函数返回的迭代器对象也称为生成器对象发电机

在这个例子中,我创建了一个生成器函数,它返回一个生成器对象<generator object fib at 0x01342480>。就像其他迭代器一样,生成器对象可以在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

因此,生成器函数是创建Iterator对象的最简单方法。

迭代器

每个生成器对象都是一个迭代器,但反之亦然。如果其类实现了__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 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)))

此外,如果检查内存占用,生成器占用的内存要少得多,因为它不需要同时将所有值存储在内存中。

每个人都有一个非常好的详细的答案和例子,我真的很感激。我只是想给那些在概念上仍然不太清楚的人简短的几行回答:

如果你创建自己的迭代器,它有点涉及-你有 创建一个类并至少实现iter和next方法。但是如果你不想经历这个麻烦并想快速创建一个迭代器怎么办?幸运的是,Python提供了一种定义迭代器的捷径。你需要做的就是定义一个至少有1次调用以产生的函数,现在当你调用该函数时,它将返回“东西”,它将像迭代器一样工作(你可以调用next方法并在for循环中使用它)。这个东西在Python中有一个名字叫Generator

希望这能澄清一点。

以前的答案错过了这个补充:生成器有close方法,而典型的迭代器没有。close方法触发生成器中的StopIteration异常,它可能被该迭代器的finally子句中捕获,以获得运行一些清理的机会。这种抽象使其在大型而非简单的迭代器中最有用。人们可以像关闭文件一样关闭生成器,而不必担心下面的内容。

也就是说,我个人对第一个问题的回答是:迭代器只有__iter__方法,典型的迭代器只有__next__方法,生成器既有__iter____next__,又有close

对于第二个问题,我个人的答案是:在公共接口中,我倾向于非常喜欢生成器,因为它更有弹性:close方法与yield from具有更大的可组合性。在本地,我可以使用迭代器,但前提是它是一个扁平且简单的结构(迭代器不容易组合),并且有理由相信序列相当短,特别是如果它可能在到达结束前被停止。我倾向于将迭代器视为低级原语,除了文字。

对于控制流来说,生成器是一个和Promise一样重要的概念:两者都是抽象的和可组合的。

强烈推荐用于迭代器和生成器的内德·巴契尔德示例

一种没有生成器的方法,可以对偶数做一些事情

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

要使用生成器…我们需要一个函数

要使用迭代器…我们需要nextiter

正如所说:

Generator函数返回一个迭代器对象

迭代器的全部好处:

每次在内存中存储一个元素

我以一种非常简单的方式专门为Python新手编写,尽管在内心深处Python做了很多事情。

让我们从最基本的开始:

考虑一个列表,

l = [1,2,3]

让我们写一个等价的函数:

def f():
return [1,2,3]

o/pprint(l): [1,2,3]& print(f()) : [1,2,3]的o/p

让我们让列表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

所以如果你观察到,一旦你让函数f成为一个生成器,它就已经是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)

如果没有另外两个概念,很难回答这个问题:iterableiterator protocol

  1. iteratoriterable有什么区别? 从概念上讲,你可以在相应的iterator的帮助下迭代iterable。在实践中有一些差异可以帮助区分iteratoriterable
    • 一个区别是iterator__next__方法,iterable没有。
    • 另一个区别-它们都包含__iter__方法。在iterable的情况下,它返回相应的迭代器。在iterator的情况下,它返回自身。 这有助于在实践中区分iteratoriterable
>>> x = [1, 2, 3]
>>> dir(x)
[... __iter__ ...]
>>> x_iter = iter(x)
>>> dir(x_iter)
[... __iter__ ... __next__ ...]
>>> type(x_iter)
list_iterator
  1. 什么是python中的iterablesliststringrange等。什么是iteratorsenumeratezipreversed等。我们可以使用上面的方法来检查这一点。这有点令人困惑。如果我们只有一种类型,可能会更容易。rangezip有什么区别吗?这样做的原因之一-range有很多附加功能-我们可以索引它或检查它是否包含一些数字等(参见详细信息python2)。

  2. 我们如何自己创建iterator?理论上我们可以实现Iterator Protocol(参见这里)。我们需要编写__next____iter__方法并引发StopIteration异常等(有关示例和可能的动机,请参阅Alex Martelli的回答,另见这里)。但在实践中我们使用生成器。到目前为止,它似乎是在python中创建iterators的主要方法。

我可以给你一些更有趣的例子,展示这些概念在实践中的一些令人困惑的用法:

  • keras中,我们有tf.keras.preprocessing.image.ImageDataGenerator;这个类没有__next____iter__方法;所以它不是迭代器(或生成器);
  • 如果你调用它的flow_from_dataframe()方法,你会得到DataFrameIterator有这些方法;但它没有实现StopIteration(这在python的内置迭代器中并不常见);在留档中,我们可能会读到“ADataFrameIterator产生(x, y)的元组”-再次混淆术语的用法;
  • 我们在keras中也有Sequence类,这是生成器功能的自定义实现(常规生成器不适合多线程),但它不实现__next____iter__,而是围绕生成器的包装器(它使用yield语句);

无代码4行备忘单:

A generator function is a function with yield in it.


A generator expression is like a list comprehension. It uses "()" vs "[]"


A generator object (often called 'a generator') is returned by both above.


A generator is also a subtype of iterator.

这个线程在许多细节中涵盖了两者之间的所有差异,但想添加一些关于两者之间概念差异的内容:

[…]GoF书籍从集合中检索项目中定义的迭代器,而发电机可以“凭空”生产物品。这就是为什么斐波那契序列生成器是一个常见的例子:无限系列的数字不能存储在集合中。

Ramalho,Luciano。流畅的Python(第415页)。O'Reilly Media。Kindle版。

当然,它并没有涵盖所有方面,但我认为它提供了一个很好的概念,当一个人可以是有用的。

可迭代对象是可以迭代的东西(自然)。然而,要做到这一点,你需要类似于迭代器对象的东西,是的,术语可能会令人困惑。可迭代对象包括一个__iter__方法,它将返回可迭代对象的迭代器对象。

迭代器对象是实现迭代器协议-一组规则的对象。在这种情况下,它必须至少有这两个方法:__iter____next____next__方法是一个提供新值的函数。__iter__方法返回迭代器对象。在更复杂的对象中,可能有一个单独的迭代器,但在更简单的情况下,__iter__返回对象本身(通常是return self)。

一个可迭代对象是list对象。它不是迭代器,但它有一个返回迭代器的__iter__方法。您可以直接将此方法称为things.__iter__(),或使用iter(things)

如果你想遍历任何集合,你需要使用它的迭代器:

things_iterator = iter(things)
for i in things_iterator:
print(i)

但是,Python会自动使用迭代器,这就是为什么你永远看不到上面的例子。相反,你写:

for i in things:
print(i)

自己编写迭代器可能很乏味,所以Python有一个更简单的替代方案:发生器函数。生成器函数不是一个普通的函数。代码不是运行代码并返回最终结果,而是延迟,函数立即返回生成器对象

生成器对象就像迭代器对象,因为它实现了迭代器协议。这对大多数目的来说已经足够了。其他答案中有很多生成器的例子。

简而言之,迭代器是一个允许您迭代另一个对象的对象,无论是集合还是其他值源。生成器是一个简化的迭代器,它或多或少完成相同的工作,但更容易实现。

通常情况下,如果生成器是您所需要的,您会选择生成器。但是,如果您正在构建一个更复杂的对象,其中包括迭代和其他功能,您将使用迭代器协议代替。

所有生成器都是迭代器,反之亦然。

from typing import Iterator
from typing import Iterable
from typing import Generator


class IT:


def __init__(self):
self.n = 0


def __iter__(self):
return self


def __next__(self):
if self.n == 4:
raise StopIteration
try:
return self.n
finally:
self.n += 1




def g():
for i in range(4):
yield i


def test(it):
print(f'type(it) = {type(it)}')
print(f'isinstance(it, Generator) = {isinstance(it, Generator)}')
print(f'isinstance(it, Iterator) = {isinstance(it, Iterator)}')
print(f'isinstance(it, Iterable) = {isinstance(it, Iterable)}')
print(next(it))
print(next(it))
print(next(it))
print(next(it))
try:
print(next(it))
except StopIteration:
print('boom\n')




print(f'issubclass(Generator, Iterator) = {issubclass(Generator, Iterator)}')
print(f'issubclass(Iterator, Iterable) = {issubclass(Iterator, Iterable)}')
print()
test(IT())
test(g())

输出:

issubclass(Generator, Iterator) = True
issubclass(Iterator, Iterable) = True


type(it) = <class '__main__.IT'>
isinstance(it, Generator) = False
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom


type(it) = <class 'generator'>
isinstance(it, Generator) = True
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom