为什么一些python内置“函数”实际上是类型?

时间:2013-02-13 19:35:47

标签: python

__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,以防差异很重要。

提前致谢。

3 个答案:

答案 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__的新类型以及具有不同类型的其他优点更容易。

因此,在enumeratereversed的情况下,这只是因为它提供了更好的性能,并且更易于维护。

其他优点包括:

  • 您可以为该类型添加方法(例如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)

注意它是如何包含诸如firstlinenofilename之类的参数,这些参数显然是由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;代码。