迭代器和生成器有什么区别?有关何时使用每种情况的一些示例会有所帮助。

#1 楼

iterator是一个更笼统的概念:其类具有__next__方法(在Python 2中为next)和执行__iter__return self方法的任何对象。每个生成器都是迭代器,但反之亦然。生成器是通过调用具有一个或多个yield表达式(在Python 2.5及更早版本中为yield语句)的函数构建的,并且该对象是满足上一段对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类,您可以轻松地提供其他方法,例如,如果您在应用程序中确实需要这种额外的功能,那么

#2 楼


迭代器和生成器有什么区别?总结一下:迭代器是具有__iter____next__(在Python 2中为next)方法的对象。生成器提供了一种简单的内置方法来创建Iterators实例。

其中包含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__方法:

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


当您尝试将对象与for循环一起使用时,将调用__iter__方法。然后,在迭代器对象上调用__next__方法,以使每个项目都进入循环。耗尽后,迭代器将引发StopIteration,并且此时无法重复使用。

从文档

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


Python的生成器提供了实现迭代器协议的便捷方法。如果将容器对象的__iter__()方法实现为生成器,它将自动返回提供__iter__()next() [Python 3中的__next__()]方法的迭代器对象(从技术上讲是生成器对象)。可以在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'


或更简单的是,Generator Expression(类似于列表推导):

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定义一个返回Generator迭代器或考虑Generator表达式的函数。

最后,请注意,生成器提供了更多作为协程的功能。我将详细解释我对“ yield”关键字的作用是什么?”的问题以及yield语句。

#3 楼

迭代器:

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

生成器:

生成器是产生或使用yield方法产生值序列。

生成器函数(例如以下示例中的next()函数)返回的生成器对象(例如以下示例中的f)的每个foo()方法调用,都会依次生成下一个值。
调用生成器函数时,它甚至不开始执行该函数就返回生成器对象。首次调用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()
>>> 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
>>>


评论


只是FYI收益率不是方法,而是关键字

–杰伊·帕里克(Jay Parikh)
17年1月30日在8:19

#4 楼

添加一个答案是因为现有答案中没有一个是专门解决官方文献中的困惑。
生成器函数是使用yield而不是return定义的普通函数。调用时,生成器函数将返回生成器对象,该对象是一种迭代器-它具有next()方法。调用next()时,将返回生成器函数产生的下一个值。
根据所读取的Python源文档,该函数或对象都可以称为“生成器”。 Python词汇表显示生成器函数,而Python Wiki隐含生成器对象。 Python教程在三个句子之间显着地暗示了这两种用法:

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

前两个句子标识具有生成器功能的生成器,尽管第三个句子用生成器对象标识了它们。
尽管存在所有这些混淆,但人们仍可以找到Python语言参考中的明确且最终的词:

yield表达式仅在定义时使用生成器函数,并且只能在函数定义的主体中使用。在函数定义中使用yield表达式足以使该定义创建一个生成器函数,而不是普通函数。
当调用生成器函数时,它将返回一个称为生成器的迭代器。然后,该生成器控制生成器功能的执行。

,因此,在正式和精确的用法中,“ generator”不合格表示生成器对象,而不是生成器功能。
上面的参考资料适用于Python 2,但Python 3语言参考说明了相同的内容。但是,Python 3词汇表指出

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


评论


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

– Blckknght
16 Mar 8 '16 at 0:27

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

– Paul
16-3-8在3:03



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

– Paul
16 Mar 8 '16 at 12:05

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

– Paul
16-3-8在22:05



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

–user712624
16年4月9日在7:05

#5 楼

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

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

希望的名称,它可以澄清一点。

#6 楼

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

话说,我个人对第一个问题的回答是:iteratable仅具有__iter__方法,典型的迭代器仅使用__next__方法,生成器同时具有__iter____next__以及附加的close

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

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

评论


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

– bli
18年7月26日在12:15

另一个答案(stackoverflow.com/a/28353158/1878788)指出“迭代器是可迭代的”。由于可迭代对象具有__iter__方法,因此迭代器为什么只能具有__next__?如果它们应该是可迭代的,我希望它们也一定具有__iter__。

– bli
18年7月26日在12:21

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

– Tino
19-10-27在7:37

#7 楼

强烈建议Ned Batchelder的示例用于迭代器和生成器

不使用生成器对偶数进行偶数运算的方法


def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them



我们不需要任何列表,也不需要return语句
有效地处理大/无限长的流...它只需走产生值

照常调用evens方法(生成器)

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n






<

迭代器


一本书充满一页是可迭代的,书签是一个迭代器



并且此书签除了移动next

num = [...]
for n in evens(num):
   do_smth(n)


无关。要使用Generator ...我们需要一个函数

来使用Iterator。 ..我们需要nextiter

如上所述:


Generator函数返回迭代器对象


的全部好处迭代器:


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


评论


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

–伊拉克。
19 Dec 9'在6:36

#8 楼


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


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

在此示例中,我创建了Generator函数,该函数返回Generator对象<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对象的最简单方法。


Iterator:


每个生成器对象都是一个迭代器,反之亦然。如果自定义迭代器对象的类实现__iter____next__方法(也称为迭代器协议),则可以创建该对象。

但是,使用generators函数创建迭代器要容易得多,因为它们可以简化迭代器的创建,但是自定义迭代器为您提供了更大的自由度,并且您还可以根据需要实现其他方法,如下面的例子。

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


#9 楼

您可以比较两种方法以获取相同的数据:

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


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

#10 楼

如果没有其他两个概念,就很难回答这个问题:iterableiterator protocol

iteratoriterable之间有什么区别?
概念上,您在相应的iterable的帮助下遍历iterator。在实践中,有一些区别可以帮助区分iteratoriterable

一个区别是iterator具有__next__方法,而iterable不具有__iter__方法。
另一个区别-它们都包含iterable方法。对于iterator,它将返回相应的迭代器。如果是iterator,它会返回自身。
在实践中可以帮助区分iterable>>> x = [1, 2, 3] >>> dir(x) [... __iter__ ...] >>> x_iter = iter(x) >>> dir(x_iter) [... __iter__ ... __next__ ...] >>> type(x_iter) list_iterator



 iterables 



python中的list是什么? stringrangeiterators等。enumerate是什么? zipreversedrange等。我们可以使用上述方法进行检查。这有点令人困惑。如果只有一种类型,可能会更容易。 ziprange之间有什么区别吗?这样做的原因之一-iterator具有许多其他功能-我们可能会对其进行索引或检查它是否包含一些数字(请参阅此处的详细信息)。


我们怎么能自己创建一个Iterator Protocol吗?从理论上讲,我们可以实现__next__(请参见此处)。我们需要编写__iter__StopIteration方法并引发iterators异常等(请参见Alex Martelli的答案以获取示例和可能的动机,另请参见此处)。但实际上,我们使用生成器。到目前为止,看来这似乎是在python中创建keras的主要方法。 >
tf.keras.preprocessing.image.ImageDataGenerator中有__next__;此类没有__iter__flow_from_dataframe()方法;因此它不是迭代器(或生成器);
如果调用其DataFrameIterator方法,则会得到具有这些方法的StopIteration;但它没有实现python(在DataFrameIterator的内置迭代器中并不常见);在文档中,我们可能会读到“产生(x, y)的元组的Sequence”-再次使术语混淆;它没有实现keras__next__,而是它是生成器的包装(它使用__iter__语句);


#11 楼

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

让我们从最基本的开始:

考虑一个列表,

l = [1,2,3]


让我们写一个等效函数:

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


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

让我们迭代列表:在python list中总是可迭代的,这意味着您可以随时使用迭代器。

让我们在列表中应用迭代器:

iter_l = iter(l) # iterator applied explicitly


让我们迭代一个函数,即编写等效的生成器函数。
在您引入关键字yield时立即在python中;

注意:每个生成器始终可以应用隐式迭代器进行迭代,此处的隐式迭代器是关键。
因此,生成器函数将是:

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(x ),它已经是int了,它将保持int(x)。

例如:
print(type(iter(iter(l))))



<class 'list_iterator'>


不要忘记这是Python而不是C或C ++。

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


列表l〜= iter(l)

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