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

时间:2010-05-05 21:14:08

标签: python iterator generator

迭代器和生成器之间有什么区别?您将使用每个案例的一些示例会有所帮助。

12 个答案:

答案 0 :(得分:458)

iterator是一个更通用的概念:任何类的类具有next方法(Python 3中为__next__)和__iter__方法return self的对象}。

每个生成器都是迭代器,但反之亦然。通过在Python 2.5及更早版本中调用具有一个或多个yield表达式(yield语句)的函数来构建生成器,并且该生成器是满足前一段{{1}的定义的对象}。

当您需要具有某种复杂状态维护行为的类时,或者想要公开除iterator(和next之外的其他方法时,您可能希望使用自定义迭代器而不是生成器。 __iter__)。大多数情况下,生成器(有时,对于足够简单的需求,生成器表达式)就足够了,并且编码更简单,因为状态维护(在合理的限制范围内)基本上是“由你完成的”框架被停职并恢复。

例如,生成器如:

__init__

或等效的生成器表达式(genexp)

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

将需要更多代码来构建自定义迭代器:

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

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

答案 1 :(得分:101)

  

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

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

具有yield的函数仍然是一个函数,当被调用时,它返回一个生成器对象的实例:

def a_function():
    "when called, returns generator object"
    yield

生成器表达式也返回一个生成器:

a_generator = (i for i in range(0))

有关更深入的说明和示例,请继续阅读。

生成器 是迭代器

具体来说,generator是迭代器的子类型。

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

Iterator 是Iterable

Iterator是一个Iterable,

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

迭代器需要 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,并且在此时无法重复使用。

来自文档

来自内置类型documentation的“迭代器类型”部分的“生成器类型”部分:

  

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

(强调补充。)

因此,我们从中了解到Generators是一种(方便的)Iterator类型。

示例迭代器对象

您可以通过创建或扩展自己的对象来创建实现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来定义返回Generator Iterator或考虑Generator Expressions的函数。

最后,请注意,生成器提供了更多功能,如协同程序。 I explain Generators, along with the yield statement, in depth on my answer to "What does the “yield” keyword do?".

答案 2 :(得分:30)

迭代器:

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

发生器:

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

生成器函数返回的每个next()方法调用生成器对象(例如,如下例中的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 :(得分:18)

添加答案,因为现有答案都没有具体解决官方文献中的混淆问题。

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

函数或对象可以被称为&#34;生成器&#34;取决于您阅读的Python源文档。 Python glossary表示生成器函数,而Python wiki表示生成器对象。 Python tutorial显着地设法暗示两个用法在三个句子的空间中:

  

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

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

尽管存在这种混淆,但人们可以找到Python language reference的明确和最后的词:

  

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

     

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

因此,在正式和精确的用法中, &#34; generator&#34;不合格是指生成器对象,而不是生成器函数。

以上参考资料适用于Python 2,但Python 3 language reference表示同样的事情。但是,Python 3 glossary表示

  

generator ...通常是指生成器函数,但在某些上下文中可能引用生成器迭代器。如果预期含义不明确,使用完整的术语可以避免含糊不清。

答案 4 :(得分:8)

每个人都有一个非常好的和冗长的答案和例子,我真的很感激。我只是想给那些在概念上还不太清楚的人提供简短的答案:

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

的名称

希望澄清一点。

答案 5 :(得分:6)

  

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

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

在这个例子中,我创建了一个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

因此,生成器函数是创建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 :(得分:5)

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

那就是说,我个人对第一个问题的回答是:iteratable只有__iter__方法,典型的迭代器只有__next__方法,生成器有__iter____next__以及其他close

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

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

答案 7 :(得分:4)

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

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

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

答案 8 :(得分:2)

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

  1. iteratoriterable有什么区别? 从概念上讲,您在相应的iterable的帮助下遍历iterator。在实践中,存在一些有助于区分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. iterables中的python是什么? liststringrange等。iterators是什么? enumeratezipreversed等。我们可以使用上述方法进行检查。这有点令人困惑。如果只有一种类型,可能会更容易。 rangezip之间有什么区别吗?这样做的原因之一-range有很多其他功能-我们可能会对其进行索引或检查它是否包含一些数字(请参阅详细信息here)。

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

我可以举几个有趣的例子,说明这些概念在实践中的用法有些混乱:

  • keras中,我们有tf.keras.preprocessing.image.ImageDataGenerator;此类没有__next____iter__方法;因此它不是迭代器(或生成器);
  • 如果调用其flow_from_dataframe()方法,您将获得具有这些方法的DataFrameIterator;但是它没有实现StopIteration(在python的内置迭代器中并不常见);在文档中,我们可能会读到“产生DataFrameIterator元组的(x, y)”-再次混淆了术语的使用;
  • 我们在Sequence中也有keras类,它是生成器功能的自定义实现(常规生成器不适合多线程),但是它没有实现__next__和{{1 }},而是生成器的包装器(它使用__iter__语句);

答案 9 :(得分:0)

强烈推荐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,我们需要一个功能

要使用Iterator ...,我们需要nextiter

如上所述:

  

Generator是一个迭代器

Iterator的全部好处:

  

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

答案 10 :(得分:0)

我用一种非常简单的方式专门为Python新手编写了代码,尽管深入的Python可以做很多事情。

让我们从最基本的开始:

考虑一个列表,

l = [1,2,3]

让我们写一个等效函数:

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

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

的o / p

让清单可迭代:在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的int(x),它将保持为int(x)。

例如:的o / p:

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

<class 'list_iterator'>

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

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

  

列表l〜= iter(l)

     

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

答案 11 :(得分:0)

无代码 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.