12.4 类型类

在 Python 中,对象都有 ob_type 属性,你可以使用内置函数 type() 获取这个属性的值:

>>> t = type("hello")
>>> t
<class 'str'>

type() 的返回结果是 PyTypeObject 的一个实例:

>>> type(t)
<class 'type'>

类型对象用于定义抽象基类的实现。

例如,对象总会实现 __repr__() 方法:

>>> class example:
...    x = 1
>>> i = example()
>>> repr(i)
'<__main__.example object at 0x10b418100>'

在任何对象的类型定义中,__repr__() 的实现总是位于相同的地址。这个位置被称之为 类型槽

类型槽

所有类型槽都定义在 Include/cpython/object.h

每个类型槽有属性名和函数签名。例如 __repr__() 函数的属性名为 tp_repr,函数签名为 reprfunc

struct PyTypeObject
---
typedef struct _typeobject {
    ...
    reprfunc tp_repr;
    ...
} PyTypeObject;

函数签名 reprfunc 定义在 Include/cpython/object.h 中,它只有一个参数 PyObject*(self)

typedef PyObject *(*reprfunc)(PyObject *);

例如,cellobject 使用函数 cell_repr 实现了 tp_repr 函数槽:

PyTypeObject PyCell_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "cell",
    sizeof(PyCellObject),
    0,
    (destructor)cell_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)cell_repr,                        /* tp_repr */
    ...
};

PyTypeObject 中除了这些以 tp_ 为前缀的的基本类型槽外,还有其他类型槽定义:

类型槽前缀

PyNumberMethods

nb_

PySequenceMethods

sq_

PyMappingMethods

mp_

PyAsyncMethods

am_

PyBufferProcs

bf_

每个类型槽都有一个独特的编号,定义在 Include/typeslots.h。当引用或获取一个对象的类型槽时,你应该使用这些常量。

例如,tp_repr 对应的常量为 Py_tp_repr,它的值是 66,其始终与类型槽的位置相匹配。这些常量在检查一个对象是否实现了一个特定的类型槽函数时非常有用。

在 C 语言中使用类型

在 C 扩展模块和 CPython 核心代码中,你会经常使用 PyObject* 类型。

举个例子,如果你在一个可取下标的对象(例如一个列表或字符串)上执行 x[n],则会调用 PyObject_GetItem(),该函数会查看对象 x 以决定如何对其进行取下标操作。

Objects/abstract.c 第 146 行

PyObject *
PyObject_GetItem(PyObject *o, PyObject *key)
{
    PyMappingMethods *m;
    PySequenceMethods *ms;
...

PyObject_GetItem() 可以用于映射类型,例如字典,也可以用于序列类型,例如列表和元组。

如果实例 o 有序列方法,那么 o->ob_type->tp_as_sequence 会被求值为真。如果实例定义了 sq_item 类型槽函数,那么就可以假设它正确实现了序列协议。

检查对象 key 是否可以被转成整数,然后使用 PySequence_GetItem() 从序列对象中取出元素。

ms = o->ob_type->tp_as_sequence;
if (ms && ms->sq_item) {
    if (PyIndex_Check(key)) {
        Py_ssize_t key_value;
        key_value = PyNumber_AsSsize_t(key, PyExc_IndexError);
        if (key_value == -1 && PyErr_Occurred())
            return NULL;
        return PySequence_GetItem(o, key_value);
    }
    else {
        return type_error("sequence index must "
                          "be integer, not '%.200s'", key);
    }
}

类型属性字典

Python 支持使用 class 关键字定义新的类型。用户定义的类型会由类型对象模块中的 type_new() 创建。

用户定义类型有一个属性字典,可以使用 __dict__() 获取。每当在一个自定义类上访问属性时,__getattr__() 的默认实现是在这个属性字典中查找。类方法、实例方法、类属性和实例属性都在这个字典中。

PyObject_GenericGetDict() 实现了获取一个对象字典实例的逻辑。PyObject_GetAttr()__getattr__() 的默认实现,同样 PyObject_SetAttr()__setattr__() 的默认实现。

参见

关于自定义类型有很多层次,这方便有大量的文档。关于 metaclass 就可以写一整本书,但是在本书中,只关注其实现。

如果你想学习更多关于元编程的内容,可以参考 Real Python 的 “Python Metaclasses”。

Last updated