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

Difference between Python's Generators and Iterators

迭代器和生成器之间有什么区别?一些例子说明你什么时候使用每种情况会很有帮助。


iterator是一个更一般的概念:类中有next方法(在python 3中为__next__方法)和__iter__方法(在python 3中为return self方法)的任何对象。

每个生成器都是一个迭代器,但不是相反。生成器是通过调用具有一个或多个yield表达式(在python 2.5和更早版本中为yield语句)的函数来构建的,并且是满足上一段对iterator的定义的对象。

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

例如,发电机,例如:

1
2
3
4
5
def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

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

1
generator = (i*i for i in range(a, b))

将需要更多的代码作为自定义迭代器生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Squares(object):
    def __init__(self, start, stop):
       self.start = start
       self.stop = stop
    def __iter__(self): return self
    def next(self):
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

但是,当然,对于类Squares,您可以很容易地提供额外的方法,即

1
2
    def current(self):
       return self.start

如果您对应用程序中的这些额外功能有任何实际需求。


What is the difference between iterators and generators? Some examples for when you would use each case would be helpful.

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

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

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

生成器表达式还返回生成器:

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

为了更深入地解释和举例,请继续阅读。

生成器是迭代器

具体来说,生成器是迭代器的一个子类型。

1
2
3
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

我们可以通过多种方式创建一个生成器。一种非常常见和简单的方法是使用函数。

具体地说,其中包含yield的函数是一个函数,当调用它时,它返回一个生成器:

1
2
3
4
5
6
7
8
>>> 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'>

生成器也是一个迭代器:

1
2
>>> isinstance(a_generator, collections.Iterator)
True

迭代器是可迭代的

迭代器是不可迭代的,

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

它需要返回迭代器的__iter__方法:

1
2
3
4
5
>>> 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__

iterables的一些示例包括内置的元组、列表、字典、集合、冻结集合、字符串、字节字符串、字节数组、范围和内存视图:

1
2
3
>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

迭代器需要next__next__方法

在Python 2中:

1
2
3
4
5
>>> 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中:

1
2
3
4
>>> 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函数从内置对象(或自定义对象)中获取迭代器:

1
2
3
>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

当尝试使用带有for循环的对象时,将调用__iter__方法。然后,在迭代器对象上调用__next__方法,以获取循环的每个项。迭代器在耗尽了EDOCX1[9]之后将其引发,此时不能重用它。

从文档中

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

Python’s generators provide a convenient way to implement the iterator protocol. If a container object’s __iter__() method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the __iter__() and next() [__next__() in Python 3] methods. More information about generators can be found in the documentation for the yield expression.

(增加了重点。)

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

迭代器对象示例

您可以通过创建或扩展自己的对象来创建实现迭代器协议的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

但简单地使用发电机来实现这一点比较容易:

1
2
3
def yes(stop):
    for _ in range(stop):
        yield 'yes'

或者更简单,一个生成器表达式(类似于列表理解):

1
yes_expr = ('yes' for _ in range(stop))

它们都可以以相同的方式使用:

1
2
3
4
5
6
7
8
9
>>> 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对象扩展为可以迭代的对象时,可以直接使用迭代器协议。

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

最后,注意生成器作为协程提供了更多的功能。我解释了生成器,以及yield语句,在我对"yield"关键字做什么的回答上进行了深入的解释。.


Iterators:

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

发电机:

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

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

当调用一个生成器函数时,它返回一个生成器对象,而不必开始执行该函数。当第一次调用next()方法时,函数开始执行,直到到达yield语句,该语句返回生成的值。收益率跟踪即记住最后一次执行。第二个next()调用从上一个值继续。

下面的示例演示yield和对generator对象的next方法调用之间的相互作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> 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
>>>


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

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

函数或对象可以被称为"生成器",这取决于您读取的是哪个Python源文档。python词汇表表示生成器函数,而python wiki则表示生成器对象。python教程在三个句子的空格中显著地暗示了这两种用法:

Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data. Each time next() is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed).

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

尽管如此,我们还是可以找到Python语言的参考,以获得清晰和最终的单词:

The yield expression is only used when defining a generator function, and can only be used in the body of a function definition. Using a yield expression in a function definition is sufficient to cause that definition to create a generator function instead of a normal function.

When a generator function is called, it returns an iterator known as a generator. That generator then controls the execution of a generator function.

所以,在正式而精确的使用中,"发电机"不合格是指发电机对象,而不是发电机功能。

上面的引用是针对python 2的,但是python 3语言的引用也说了同样的话。但是,python 3术语表指出

generator ... Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.


每个人都有一个非常好和详细的例子回答,我非常感谢。我只是想给那些概念上还不太清楚的人一个简短的回答:

如果您创建自己的迭代器,它有点复杂——您已经创建一个类,至少实现ITER和下一个方法。但如果您不想经历这些麻烦,并且希望快速创建迭代器,该怎么办呢?幸运的是,Python提供了一种定义迭代器的快捷方式。您所需要做的就是定义一个函数,其中至少有一个要生成的调用,现在当您调用该函数时,它将返回"something",其作用类似于迭代器(您可以调用下一个方法并在for循环中使用它)。这个东西在python中有个名字叫做generator

希望能澄清一点。


Generator Function, Generator Object, Generator:

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

在这个例子中,我创建了一个生成器函数,它返回一个生成器对象。与其他迭代器一样,generator对象可以在for循环中使用,也可以与内置函数next()一起使用,后者返回generator的下一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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__方法(也称为迭代器协议),则可以创建该对象。

但是,使用generator函数创建迭代器要容易得多,因为它们简化了迭代器的创建,但是自定义迭代器给您更多的自由,您还可以根据您的需求实现其他方法,如下面的示例所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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


以前的回答没有注意到这一点:一个生成器有一个close方法,而典型的迭代器没有。close方法在生成器中触发一个StopIteration异常,该异常可能在迭代器中的finally子句中被捕获,以便有机会运行一些clean?起来。这种抽象使得它在大型迭代器中最有用。可以像关闭文件一样关闭生成器,而不必为下面的内容操心。

也就是说,我个人对第一个问题的回答是:可迭代只有一个__iter__方法,典型的迭代器只有一个__next__方法,生成器同时有一个__iter__和一个__next__方法和一个额外的close方法。

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

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


对于相同的数据,可以比较两种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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)))

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