【问题标题】:Does enumerate() produce a generator object?enumerate() 会生成生成器对象吗?
【发布时间】:2014-07-03 00:21:26
【问题描述】:

作为一个完整的 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 只能迭代一次,a this famous python-tag answer中强调的生成器的特性。

另一方面,this question 的两个最受好评的答案 关于如何确定一个对象是否是一个生成器似乎 表示 enumerate() 确实返回生成器。

import types
import inspect

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

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

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

...虽然该问题的第三个poorly-upvoted answer 似乎表明enumerate() 确实实际上返回了一个生成器:

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

isgenerator(x)
# Out[8]: True

那么发生了什么? x 是不是生成器?是不是某种意义上 “类似发电机”,但不是真正的发电机? Python 的使用是否 鸭子打字意味着上面最终代码块中概述的测试 实际上是最好的吗?

而不是继续写下贯穿我的可能性 头,我会把这个扔给你们中的人,他们会立即 知道答案。

【问题讨论】:

  • 它像鸭子一样嘎嘎叫吗?
  • 有点类似于xrange() 也不是GeneratorType,但它确实表现得像一个生成器
  • 这可能有用:stackoverflow.com/questions/2776829/… -- 看起来 Python 区分了迭代器和生成器
  • “作为一个完整的 Python 新手”,您肯定会过度关注确切的类型。 ;)

标签: python


【解决方案1】:

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

【讨论】:

  • 非常有趣。只是为了确保我明白这一点,迭代器(或可迭代对象)是由它们的行为定义的,而实际的“生成器”和“枚举”类定义了这些对象。 “枚举”类对象与“生成器”类对象共享许多行为,但许多其他类的对象也是如此。有没有一种简单的方法可以找出这些“通用生成器”类中的任何一个是否动态生成其元素(如真正的生成器),而不是将它们存储在内存中?
  • @JoshO'Brien 有点吹毛求疵:“可迭代”是可以迭代的任何对象,例如列表、字典、字符串、文件。 “迭代器”是实际创建以迭代可迭代对象的对象。对于大多数 Python 容器,您可以通过调用 iter(obj) 来获取它。当您执行for x in obj 时,这会隐含地发生。编辑:我看到 John Y. 打败了我 :)
  • @dano:你的评论更好。我刚刚删除了我的,因为您正在编辑您的以提及我的。不过,这里又是glossary link,因为无论如何这都很有用。
  • @JoshO'Brien 我不知道有什么方法可以确定给定迭代器返回的数据是延迟获取还是完全加载到内存中。迭代器只是提供了一种通过公开next() 方法一次(且仅一次)一次迭代对象的方法。调用者不知道 next() 内部究竟发生了什么。
  • @JohnY -- 感谢您保留词汇表链接。你的两个cmets都有很大帮助。刚刚比较了iter(range(9))iter(xrange(9))iter([2,1])iter((2,1))iter(enumerate([2,1])),这很有启发性。仍然不完全确定为什么 enumerategenerator 对象会通过迭代而“用完”,而其他类型则不会,但我想这只是它们的实现和作者决定的问题这种行为是可取的。编辑:好的——划掉最后一点。我刚刚想通了。现在一切都说得通了
【解决方案2】:

枚举类型的测试:

我会在探索枚举类型以及它如何适应 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

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

来源:

source中,我们可以看到迭代返回the tuple的枚举对象(PyEnum_Type)和ABC module we can see that any item with a next and __iter__ method (actually, attribute) is defined to be an iterator.中的(__next__ in Python 3.

标准库测试

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

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

所以我们知道枚举类型是迭代器。 But we see that a Generator type is created by a function with yieldin the documentationor a generator expression。所以生成器是迭代器,因为它们具有next__iter__ 方法,但并非所有迭代器都必须是生成器(需要sendclosethrow 的接口),正如我们所见这个枚举对象。

那么我们对enumerate 了解多少?

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

从文档中我们也知道生成器类型只是“provide a convenient way to implement the iterator protocol.”因此,生成器是迭代器的子集。此外,这使我们能够得出以下概括:

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

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

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

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

那么要测试什么?

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

如果您需要枚举类型,您可能希望允许具有整数索引的元组的迭代器或迭代器,以下将返回 True

isinstance(g, collections.Iterable)

如果你只想要一个枚举类型:

isinstance(e, enumerate)

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

【讨论】:

    【解决方案3】:

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

    是的,是的。你不应该真正关心它是否是一只鸭子,只要它走路、说话和闻起来像一只鸭子。它也一个生成器,不应该有真正的区别。

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

    【讨论】:

      【解决方案4】:

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

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

      正如 Daniel 指出的,它是它自己的类型,enumerate。这种类型恰好是可迭代的。生成器也是可迭代的。您引用的第二个被否决的答案基本上只是通过谈论__iter__ 方法间接地指出了这一点。

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

      因此,与其说enumerate 类型的东西“类似于生成器”,不如简单地说enumerateGeneratorType 类都是可迭代的(以及列表等)更有意义。 如何它们迭代数据(以及它们存储的数据的形状)可能完全不同,但界面是相同的。

      希望有帮助!

      【讨论】:

      • 谢谢,这确实很有帮助。
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2022-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-10
        • 1970-01-01
        • 1970-01-01
        • 2016-12-05
        • 2019-10-18
        相关资源
        最近更新 更多