__builtin__
模块中的许多迭代器“函数”实际上都是作为类型实现的,即使文档将它们称为“函数”。以enumerate
为例。文档说它等同于:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
当然,这与我实施的完全相同。但是,我使用之前的定义运行了以下测试,并得到了这个:
>>> x = enumerate(range(10))
>>> x
<generator object enumerate at 0x01ED9F08>
这是我的期望。但是,在使用__builtin__
版本时,我得到了这个:
>>> x = enumerate(range(10))
>>> x
<enumerate object at 0x01EE9EE0>
由此我推断它被定义为
class enumerate:
def __init__(self, sequence, start=0):
# ....
def __iter__(self):
# ...
而不是文档中显示的标准形式。现在我可以理解它是如何工作的,以及它与标准形式的对应关系,我想知道的是这样做的原因是什么。这种方式更有效吗?它是否与在C中实现的这些函数有关(我不知道它们是否存在,但我怀疑如此)?
我正在使用Python 2.7.2,以防差异很重要。
提前致谢。
答案 0 :(得分:9)
是的,它与内置函数通常用C实现的事实有关。通常C代码将引入新类型而不是普通函数,如enumerate
的情况。
用C语言编写它们可以更好地控制它们,并且通常会有一些性能改进,
而且由于没有真正的缺点,这是一个自然的选择。
考虑到写下相当于:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
在C中,即生成器的新实例,您应该创建一个包含实际字节码的代码对象。这并非不可能,但并不比编写一个简单地实现调用Python C-API的__iter__
和__next__
的新类型以及具有不同类型的其他优点更容易。
因此,在enumerate
和reversed
的情况下,这只是因为它提供了更好的性能,并且更易于维护。
其他优点包括:
chain.from_iterable
)。即使使用函数也可以这样做,但是您必须先定义它们,然后手动设置属性,这看起来不那么干净。isinstance
。这可以允许一些优化(例如,如果您知道isinstance(iterable, itertools.repeat)
,那么您可以优化代码,因为您知道将产生哪些值。编辑:只是为了澄清我的意思:
在C中的,即生成器的新实例,您应该创建一个代码 包含实际字节码的对象。
查看Objects/genobject.c
创建PyGen_Type
实例的唯一功能是PyGen_New
,其签名为:
PyObject *
PyGen_New(PyFrameObject *f)
现在,查看Objects/frameobject.c
我们可以看到要创建PyFrameObject
,必须调用PyFrame_New
,其中包含此签名:
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
PyObject *locals)
正如您所见,需要 PyCodeObject
个实例。 PyCodeObject
是python解释器在内部表示字节码的方式(例如,PyCodeObject
可以表示函数的字节码),所以:是,从C创建PyGen_Type
实例你是必须手动创建字节码,由于PyCodeObject
具有此签名,因此创建PyCode_New
并不容易:
PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars,
PyObject *filename, PyObject *name, int firstlineno,
PyObject *lnotab)
注意它是如何包含诸如firstlineno
,filename
之类的参数,这些参数显然是由python源而不是其他C代码获得的。显然你可以在C中创建它,但我不能确定它比写一个简单的新类型需要更少的字符。
答案 1 :(得分:2)
是的,它们是用C实现的。它们使用C API作为迭代器(PEP 234),其中通过创建具有tp_iternext
槽的新类型来定义迭代器。
生成器函数语法(yield
)创建的函数是返回特殊生成器对象的“魔法”函数。这些是types.GeneratorType
的实例,您无法手动创建。{1}}。如果使用C API的其他库定义了自己的迭代器类型,它将不是GeneratorType
的实例,但它仍将实现C API迭代器协议。
因此,enumerate
类型是与GeneratorType
不同的不同类型,您可以像使用isinstance
这样的任何其他类型使用它(尽管您不应该)。
与Bakuriu的回答不同,enumerate
不是生成器,因此没有字节码/帧。
$ grep -i 'frame\|gen' Objects/enumobject.c
PyObject_GenericGetAttr, /* tp_getattro */
PyType_GenericAlloc, /* tp_alloc */
PyObject_GenericGetAttr, /* tp_getattro */
PyType_GenericAlloc, /* tp_alloc */
相反,您创建新枚举对象的方式是使用函数enum_new
,其签名不使用框架
static PyObject *
enum_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
此函数位于tp_new
结构PyEnum_Type
结构的PyTypeObject
槽内(类型为tp_iternext
)。在这里,我们还看到enum_next
槽被PyEnum_Type
函数占用,它包含简单的C代码,它获取它所枚举的迭代器的下一项,然后返回一个PyObject(一个元组)
接着,Python/bltinmodule.c
会被放入名为enumerate
的内置模块(generatortype
)中,以便公开访问。
不需要字节码。纯C.比任何纯python或{{1}}实现更有效。
答案 2 :(得分:1)
enumerate
调用需要返回迭代器。迭代器是具有特定API的对象。使用特定API实现类的最简单方法通常是将其实现为类。
它说&#34;键入&#34;而不是&#34; class&#34;是特定于Python 2的,因为内置类被称为&#34;类型&#34;在Python 2中,作为Python的其余部分,在Python 2.2之前具有类型和类。在Python 2.3中,类和类型是统一的。因此,在Python 3中它表示类:
>>> enumerate
<class 'enumerate'>
这更清楚你的问题&#34;为什么有些内置类型而不是函数&#34; 与它们在C中实现它们几乎没什么关系。它们是类型/类,因为这是实现功能的最佳方式。这很容易。
现在,如果我们将您的问题解释为&#34;为什么enumerate
是类型/类而不是生成器&#34; (这是一个非常不同的问题),那么答案也自然不同。答案是,生成器是用于从Python函数创建迭代器的Python快捷方式。它们不打算在C中使用。它们对于使用函数创建生成器比使用类外方法更有用,就好像你想要从类方法中创建一个迭代器对象,你需要传入对象上下文,但是有了功能,你不需要这个。因此,你所拥有的主要好处是“脚手架”#34;代码。