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


Answers:


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__和方法的对象__next__next在Python 2中)。生成器提供了一种简单的内置方法来创建Iterator的实例。

包含yield的函数仍然是一个函数,在调用该函数时,它会返回生成器对象的实例:

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

我们可以通过几种方式创建生成器。一种非常普遍且简单的方法是使用函数。

具体来说,其中包含yield的函数是一个函数,在调用该函数时会返回生成器:

>>> 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__返回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__

内置元组,列表,字典,集合,冻结集合,字符串,字节字符串,字节数组,范围和内存视图是可迭代对象的一些示例:

>>> 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__当您尝试将对象与for循环一起使用时,将调用该方法。然后__next__,在迭代器对象上调用该方法以获取循环中的每个项目。StopIteration耗尽后,迭代器会上升,并且此时无法重用。

从文档中

在“内置类型” 文档的“迭代器类型”部分的“生成器类型”部分中:

Python的生成器提供了一种实现迭代器协议的便捷方法。如果容器对象的__iter__()方法作为发电机实现的,它会自动返回一个迭代器对象(在技术上,一个生成器对象)供给__iter__()next()[ __next__()在Python 3]的方法。有关生成器的更多信息,可以在yield表达式的文档中找到。

(已添加重点。)

因此,我们从中了解到生成器是(便捷的)迭代器类型。

示例迭代器对象

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

但是,使用Generator来执行此操作会更容易:

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”关键字的作用?”的回答中深入解释Generators和该语句。


41

迭代器:

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

发电机:

生成器是一种使用yield方法生成或产生值序列的函数。

生成器函数(如以下示例中的ex:函数)返回的生成next()器对象(如ex f中的示例)的每个方法调用都将按foo()顺序生成下一个值。

调用生成器函数时,它甚至不开始执行函数就返回生成器对象。当next()方法被称为首次,函数开始执行,直到它到达它返回产生值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()
>>> 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
只是FYI收益率不是方法,而是关键字
Jay Parikh

25

添加答案是因为现有答案中没有一个专门解决官方文献中的混乱。

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

函数或对象都可以称为“生成器”,具体取决于您阅读的是哪个Python源文档。在Python的词汇说发生器功能,而Python的维基意味着发电机对象。在Python的教程非常设法暗示用三句话的空间用法:

生成器是用于创建迭代器的简单而强大的工具。它们的编写方式与常规函数类似,但是只要要返回数据就使用yield语句。每次在其上调用next()时,生成器都会从上次中断的地方继续(它会记住所有数据值以及最后执行的语句)。

前两个句子用生成器函数标识生成器,而第三句话用生成器对象标识它们。

尽管存在所有这些困惑,但您仍然可以找到Python语言参考来获得清晰明确的词:

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

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

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

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

generator ...通常是指生成器函数,但在某些情况下可能是指生成器迭代器。在预期含义不明确的情况下,使用完整术语可以避免歧义。


我认为生成器函数和生成器对象之间没有太多混淆,出于同样的原因,类及其实例之间通常没有混淆。在这两种情况下,您都叫一个获得另一种,在随意交谈(或快速撰写的文档)中,您可以使用类名或单词“ generator”。在您所谈论的罕见情况下,只需要对“发电机功能”与“发电机对象”进行明确说明即可。
Blckknght

6
1.不管为什么不应该造成混淆的理论原因,对此问题的其他答案的评论都否认并且彼此矛盾,没有解决方案,表明存在实际的混淆。2.随便的不精确行为是可以的,但精确,权威的消息来源至少应该是SO的选择之一。我在当前的项目中广泛使用了生成器函数和对象,在设计和编码时,区别非常重要。很高兴知道现在使用什么术语,因此以后不必更改数十个变量名和注释。
保罗

2
想象一下在数学文献中,函数与函数的返回值之间没有区别。偶尔将它们非正式地合并起来很方便,但是却增加了发生各种错误的风险。如果不按照惯例,语言和符号来对这种区分进行形式化,那么高级现代数学将受到极大和不必要的阻碍。
Paul

2
绕过生成器的高阶函数或生成器函数听起来可能很奇怪,但是对我来说,它们已经出现了。我在Apache Spark中工作,它具有非常实用的编程风格。这些函数必须创建,传递和传递各种对象才能完成任务。在很多情况下,我无法了解正在使用哪种“发电机”。使用一致且正确的术语,变量名和注释中的提示有助于消除混淆。一个Pythonist的晦涩之处可能是另一个人的项目设计的中心!
Paul

1
@Paul,感谢您编写此答案。这种混淆很重要,因为生成器对象与生成器函数之间的差异是获得所需行为与必须查找生成器之间的差异。
blujay

16

每个人都有一个非常好的示例冗长的答案,我对此表示感谢。我只是想为在概念上仍不太清楚的人提供简短的答案:

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

希望可以澄清一下。


9

先前的答案未添加此功能:生成器具有close方法,而典型的迭代器则没有。该close方法StopIteration在生成器中触发一个异常,该异常可能会finally在该迭代器的子句中捕获,从而有机会运行一些清理操作。这种抽象使它比简单的迭代器在大型迭代器中最有用。一个人可以关闭一个生成器,就像一个人可以关闭一个文件一样,而不必担心底层内容。

也就是说,我对第一个问题的个人回答是:iteratable __iter__仅具有一个方法,典型的迭代器__next__仅具有一个方法,生成器具有an __iter__和a __next__以及一个extra close

对于第二个问题,我个人的回答是:在公共界面中,我倾向于偏爱生成器,因为它更具弹性:该close方法具有更大的可组合性yield from。在本地,我可以使用迭代器,但前提是它是一个平面且简单的结构(迭代器不容易编写),并且有理由相信该序列很短,尤其是在序列结束之前可以将其停止的情况。我倾向于将迭代器视为低级原语,而不是文字。

对于控制流而言,生成器是一个与承诺一样重要的概念:两者都是抽象的且可组合的。


您能否举一个例子来说明您在谈论构图时的意思?另外,您能否在谈论“ 典型的迭代器” 时解释您的想法?
bli

1
另一个答案(stackoverflow.com/a/28353158/1878788)指出“迭代器是可迭代的”。既然一个可迭代的对象有一个__iter__方法,那么迭代器怎么可能__next__只有一个?如果它们应该是可迭代的,我希望它们也一定有__iter__
bli

1
@bli:AFAICS这里的答案是指标准PEP234,所以它是正确的,而另一个答案是指某些实现,因此是有问题的。该标准仅要求__iter__on迭代器返回迭代器,而迭代器仅需要一个next方法(__next__在Python3中)。请不要将标准(用于鸭子输入)与它们的实现(特定的Python解释器如何实现)混淆。这有点像生成器函数(定义)和生成器对象(实现)之间的混淆。;)
蒂诺

7

生成器功能,生成器对象,生成器:

一个发电机的功能就像Python中的常规功能,但它包含一个或多个yield语句。生成器函数是一个很好的工具,它可以尽可能轻松地创建 Iterator对象。通过generator函数返回的Iterator对象也称为Generator对象Generator

在此示例中,我创建了一个Generator函数,该函数返回Generator对象<generator object fib at 0x01342480>。就像其他迭代器一样,Generator对象可以在for循环中使用,也可以与内置函数一起使用,该 函数next()从generator返回下一个值。

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

6

强烈推荐Ned Batchelder的示例 用于迭代器和生成器

没有生成器的方法会做一些偶数运算

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 ...我们需要一个函数

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

如前所述:

Generator函数返回迭代器对象

迭代器的全部优点:

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


关于您的第一个代码段,我想知道arg'stream'除了list []之外还有什么?
伊克拉。

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为print(l): [1,2,3]&o / p为print(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 generator,它已经是iter(f)

现在,

l是列表,应用迭代器方法“ iter”后,它变为iter(l)

f已经是iter(f),在应用迭代器方法“ iter”之后,它变为iter(iter(f)),再次是iter(f)

您将int强制转换为已经为int的int(x)并保留为int(x)。

例如:

print(type(iter(iter(l))))

<class 'list_iterator'>

永远不要忘记这是Python,而不是C或C ++

因此,以上解释得出的结论是:

列出l〜= iter(l)

生成器函数f == iter(f)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.