关于python:enumerate()是否生成了一个生成器对象?

Does enumerate() produce a generator object?

作为一个完整的python新手,它看起来的确如此。运行以下…

1
2
3
4
5
6
7
8
9
x = enumerate(['fee', 'fie', 'foe'])
x.next()
# Out[1]: (0, 'fee')

list(x)
# Out[2]: [(1, 'fie'), (2, 'foe')]

list(x)
# Out[3]: []

…我注意到:(a)x确实有next方法,似乎发电机需要,和(b)x只能迭代一次,a这个著名的python标签强调了发电机的特性。回答。

另一方面,这两个最乐观的答案问题关于如何确定对象是否是生成器表示enumerate()不返回发电机。

1
2
3
4
5
6
7
8
9
10
import types
import inspect

x = enumerate(['fee', 'fie', 'foe'])

isinstance(x, types.GeneratorType)
# Out[4]: False

inspect.isgenerator(x)
# Out[5]: False

…对这个问题的第三个糟糕的上票回答似乎表明,enumerate()实际上返回了一个生成器:

1
2
3
4
5
def isgenerator(iterable):
    return hasattr(iterable,'__iter__') and not hasattr(iterable,'__len__')

isgenerator(x)
# Out[8]: True

怎么了?x是不是发电机?从某种意义上说"类似发电机",但不是实际的发电机?Python使用duck输入意味着上面最后一个代码块中概述的测试实际上是最好的吗?

而不是继续写下通过我的头,我就把这个扔给你们这些人,他们会马上知道答案。


虽然python文档说enumerate在功能上等同于:

1
2
3
4
5
def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

real enumerate函数返回迭代器,但不返回实际的生成器。创建enumerate对象后,如果调用help(x)可以看到:

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
>>> x = enumerate([1,2])
>>> help(x)
class enumerate(object)
 |  enumerate(iterable[, start]) -> iterator for index, value of iterable
 |  
 |  Return an enumerate object.  iterable must be another object that supports
 |  iteration.  The enumerate object yields pairs containing a count (from
 |  start, which defaults to zero) and a value yielded by the iterable argument.
 |  enumerate is useful for obtaining an indexed list:
 |      (0, seq[0]), (1, seq[1]), (2, seq[2]), ...
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |  
 |  __iter__(...)
 |      x.__iter__() <==> iter(x)
 |  
 |  next(...)
 |      x.next() -> the next value, or raise StopIteration
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T

在Python中,生成器基本上是一种特定类型的迭代器,通过使用yield从函数返回数据来实现。但是,enumerate实际上是在C中实现的,而不是纯Python,因此不涉及yield。您可以在这里找到源代码:http://hg.python.org/cpython/file/2.7/objects/enumobject.c


枚举类型测试:

我将在探索枚举类型以及它如何适合于Python语言的过程中包括这个重要的测试:

1
2
3
4
5
6
7
8
>>> import collections
>>> e = enumerate('abc')
>>> isinstance(e, enumerate)
True
>>> isinstance(e, collections.Iterable)
True
>>> isinstance(e, collections.Iterator)
True

但我们看到:

1
2
3
>>> import types
>>> isinstance(e, types.GeneratorType)
False

所以我们知道枚举对象不是生成器。

资料来源:

在源代码(python 2.7)中,我们可以看到迭代返回元组的枚举对象(pyenum-type),在abc模块中,我们可以看到具有next__iter__方法(实际上是属性)的任何项都被定义为迭代器。(python 3中的__next__。)

标准库测试

因此抽象基类库使用以下测试:

1
2
>>> hasattr(e, 'next') and hasattr(e, '__iter__')
True

所以我们知道枚举类型是迭代器。但是我们看到生成器类型是由文档或生成器表达式中具有yield的函数创建的。所以生成器是迭代器,因为它们有next__iter__方法,但并非所有迭代器都必须是生成器,正如我们在这个枚举对象中看到的那样。

那么我们对enumerate了解多少?

从文档和源代码中,我们知道enumerate返回一个enumerate对象,并且通过定义我们知道它是一个迭代器,即使我们的测试声明它显式地不是一个生成器。

我们还从文档中了解到,生成器类型只是"提供了实现迭代器协议的方便方法"。因此,生成器是迭代器的一个子集。此外,这允许我们得出以下概括:

All generators are iterators, but not all iterators are generators.

因此,当我们可以使枚举对象成为生成器时:

1
2
3
>>> g = (i for i in e)
>>> isinstance(g, types.GeneratorType)
True

我们不能指望它本身就是一个发电机,所以这是一个错误的测试。

那么测试什么呢?

这意味着你不应该测试一个生成器,你应该使用我提供的第一个测试,而不是重新实现标准库(我希望我今天可以不做这件事):

如果需要枚举类型,则可能需要允许具有整数索引的元组的iterables或迭代器,并且下面将返回True

1
isinstance(g, collections.Iterable)

如果只需要一个枚举类型:

1
isinstance(e, enumerate)

ps如果您感兴趣,这里是generators的源代码实现:http://hg.python.org/cpython/file/785e29d8ce13/objects/genobject.c


Is it in some sense"generator-like", but not an actual generator?

是的,是的。你不应该真的在乎它是不是一只鸭子,但只要它走路、说话、闻起来像鸭子。它就像一台发电机,不应该有什么区别。

当您想要扩展功能时,通常使用类似于生成器的类型而不是实际的生成器。例如,range也类似于生成器,但它也支持y in range(x)len(range(x))(python2.x中的xrange)。


您可以尝试一些事情来证明它既不是生成器也不是生成器的子类:

1
2
3
4
5
6
>>> x = enumerate(["a","b","c"])
>>> type(x)
<type 'enumerate'>
>>> import types
>>> issubclass(type(x), types.GeneratorType)
False

正如丹尼尔指出的,这是它自己的类型,enumerate。这种类型恰好是不可测的。发电机也是不可替代的。第二,你提到的投反对票的答案基本上只是间接地指出,通过谈论__iter__方法。

因此,它们实现了一些相同的方法,因为它们都是不可重复的。就像列表和生成器都是不可重复的,但它们并不相同。

因此,与其说enumerate类型的类是"类似于生成器"的,不如简单地说enumerateGeneratorType类都是可重写的(以及列表等)。它们如何迭代数据(以及它们存储的数据的形状)可能非常不同,但接口是相同的。

希望有帮助!


enumerate生成一个枚举对象。它是一个迭代器,就像一个生成器。