enumerate() 会产生生成器对象吗?

32 投票
5 回答
9966 浏览
提问于 2025-04-18 06:35

作为一个完全的新手,使用Python确实让我感到困惑。我运行了以下代码...

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 只能被迭代一次,这是生成器的一个特点,在这个著名的 python 标签回答中强调过。

另一方面,关于如何判断一个对象是否是生成器的这个问题,有两个点赞数最高的回答似乎表明 enumerate() 并不 返回一个生成器。

import types
import inspect

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

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

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

... 而第三个点赞数较少的回答似乎表明 enumerate() 确实 返回一个生成器:

def isgenerator(iterable):
    return hasattr(iterable,'__iter__') and not hasattr(iterable,'__len__')

isgenerator(x)
# Out[8]: True

那么到底是怎么回事呢?x 是生成器吗?还是说它在某种意义上“像生成器”,但并不是真正的生成器?Python的鸭子类型使用是否意味着上面最后一个代码块中提到的测试实际上是最好的方法?

与其继续写下我脑海中浮现的各种可能性,不如直接抛给那些能立刻知道答案的朋友们。

5 个回答

2

enumerate 是一个用来生成枚举对象的工具。它就像一个迭代器,类似于生成器。

3

你可以尝试一些方法来证明它既不是生成器,也不是生成器的子类:

>>> x = enumerate(["a","b","c"])
>>> type(x)
<type 'enumerate'>
>>> import types
>>> issubclass(type(x), types.GeneratorType)
False

正如丹尼尔所指出的,它是自己的类型,叫做 enumerate。这个类型恰好是可迭代的。生成器也是可迭代的。你提到的那个被投票反对的答案,基本上就是通过谈论 __iter__ 方法间接指出这一点。

所以它们实现了一些相同的方法,因为它们都是可迭代的。就像列表和生成器都是可迭代的,但它们并不是同一种东西。

因此,与其说类型为 enumerate 的东西是“像生成器”,不如直接说 enumerateGeneratorType 类都是可迭代的(还有列表等)。它们如何遍历数据(以及存储的数据形状)可能会有很大不同,但接口是相同的。

希望这能帮到你!

4

它在某种意义上是“像生成器”,但并不是真正的生成器吗?

是的,没错。你其实不需要太在意它是不是一只鸭子,关键是它走路、说话和闻起来像一只鸭子就行。它也可以是一个生成器,这并不会有什么实质性的区别。

通常,当你想要扩展功能时,会使用类似生成器的类型,而不是实际的生成器。比如说,range 也是类似生成器的,但它还支持一些其他功能,比如 y in range(x)len(range(x))(在python2.x中是 xrange)。

14

测试枚举类型:

在探索枚举类型以及它在Python语言中的作用时,我会加入这个重要的测试:

>>> import collections
>>> e = enumerate('abc')
>>> isinstance(e, enumerate)
True
>>> isinstance(e, collections.Iterable)
True
>>> isinstance(e, collections.Iterator)
True

但是我们看到:

>>> import types
>>> isinstance(e, types.GeneratorType)
False

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

来源:

源代码中,我们可以看到枚举对象(PyEnum_Type)是一个可以逐个返回元组的对象,并且在ABC模块中,我们可以看到任何具有next__iter__方法(实际上是属性)的项目都被定义为迭代器。(在Python 3中是__next__

标准库测试

所以抽象基类库使用了以下测试:

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

因此我们知道枚举类型是迭代器。但是我们看到生成器类型是由带有yield的函数创建的 在文档中 或者生成器表达式。所以生成器是迭代器,因为它们有next__iter__方法,但并不是所有的迭代器都是生成器(接口要求有sendclosethrow),正如我们在这个枚举对象中看到的。

那么我们对enumerate知道什么?

根据文档和源代码,我们知道枚举返回一个枚举对象,并且根据定义它是一个迭代器,即使我们的测试表明它明确不是生成器。

我们还从文档中知道,生成器类型只是“提供了一种方便的方式来实现迭代器协议。”因此,生成器是迭代器的一个子集。此外,这让我们得出以下概括:

所有生成器都是迭代器,但并不是所有迭代器都是生成器。

所以虽然我们可以把我们的枚举对象变成生成器:

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

但我们不能指望它本身就是一个生成器,所以这将是错误的测试。

那么该测试什么?

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

如果你需要一个枚举类型,你可能想要允许具有整数索引的元组的可迭代对象或迭代器,以下将返回True

isinstance(g, collections.Iterable)

如果你只想要特定的枚举类型:

isinstance(e, enumerate)

顺便说一下,如果你感兴趣,这里是生成器的源代码实现:https://github.com/python/cpython/blob/master/Objects/genobject.c
这是生成器抽象基类(ABC):https://github.com/python/cpython/blob/master/Lib/_collections_abc.py#L309

35

虽然Python的文档说enumerate的功能和下面这个是一样的:

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

但实际上,enumerate函数返回的是一个迭代器,而不是一个真正的生成器。如果你在创建了一个enumerate对象后调用help(x),就能看到这一点:

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

撰写回答