读 Python behind the scenes #6: how Python object system works

碎碎念:上周六参加了 pycon 2026,令人印象深刻的是:语言特性的分会场有两位高中生嘉宾;最火热的话题是 free threading。但相比 Agent 分会场的座无虚席,这边显得过分冷清,倒是令人嘘唏。
https://tenthousandmeters.com/blog/python-behind-the-scenes-6-how-python-object-system-works/
从第一天学习 Python 开始,我们便知道 Python 中万物皆对象,这篇文章将介绍 Python 对象的实现原理(cpython3.9)。
背景
想象下面的一段代码中在 cpython 中是怎么处理的呢?第一时间映入你脑中的大概率是 __add__() 方法。
def f(x):
return x + 7
然而,实际情况会稍微复杂一点,因为 x 可能是一个类,也可能是一个 builtin type int —— 不会调用任何魔术方法(special methods)。
Python 对象与类型
在追踪源码前,先了解 Python 对象 与 Python 类型 在 cpython 中的定义:
PyObject
typedef struct _object {
_PyObject_HEAD_EXTRA // macro 宏 - 供调试使用
Py_ssize_t ob_refcnt; // 引用计数
PyTypeObject *ob_type; // 对象类型
} PyObject;
float 对象定义的例子:
typedef struct {
PyObject ob_base;
double ob_fval;
} PyFloatObject;
PyTypeObject
万物皆对象的一个好处:VM 不需要去写一堆 if/else 判断类型,直接调用「类型」 PyTypeObject 已经定义的操作执行。
简而言之,「类型」不仅仅只定义了类型,还具体定义了 python 对象的「行为」:
struct _typeobject {
// 类型本质上也是一个 python object
PyVarObject ob_base;
...
// 类型定义一组数值操作,例如 相加/相减/...
PyNumberMethods *tp_as_number;
...
};
PyNumberMethods 定义了行为的具体实现:
typedef struct {
binaryfunc nb_add;
binaryfunc nb_subtract;
binaryfunc nb_multiply;
...
} PyNumberMethods;
BYTECODE
按照惯例,从 bytecode 开始,看看 VM 具体的执行逻辑:
LOAD_FAST:首先将x的 value 放入 value stack 中LOAD_CONST:同上将7常量放入 stackBINARY_ADD:将前两个值从 stack 中 pop 出来,相加后,放回 stackRETURN_VALUE:最终将值从 stack 中 pop 并返回
$ python -m dis f.py
...
2 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (7)
4 BINARY_ADD
6 RETURN_VALUE
P.S. BINARY_ADD —— 对于 VM 而言,不管是变量 x 还是常量 7,都是 Python 对象(PyObject),所以 VM 只需让不通类型的对象来执行操作,例如 int 类型自己知道如何相加 integers。
BINARY_ADD
下面是 BINARY_ADD opcode 在 Python/ceval.c 中的定义
case TARGET(BINARY_ADD): {
PyObject *right = POP();
PyObject *left = TOP();
PyObject *sum;
/* NOTE(haypo): Please don't try to micro-optimize int+int on
CPython using bytecode, it is simply worthless.
See http://bugs.python.org/issue21955 and
http://bugs.python.org/issue10044 for the discussion. In short,
no patch shown any impact on a realistic benchmark, only a minor
speedup on microbenchmarks. */
if (PyUnicode_CheckExact(left) &&
PyUnicode_CheckExact(right)) {
// 1. String 的特殊优化(fast path)
// >>> output = "abc"
// >>> output += "def"
// 'abcdef'
sum = unicode_concatenate(tstate, left, right, f, next_instr);
/* unicode_concatenate consumed the ref to left */
}
else {
// 2. 主流的入口
sum = PyNumber_Add(left, right);
Py_DECREF(left);
}
Py_DECREF(right);
SET_TOP(sum);
if (sum == NULL)
goto error;
DISPATCH();
}
PyNumber_Add
继续追踪 PyNumber_Add 的实现:
PyObject *
PyNumber_Add(PyObject *v, PyObject *w)
{
// 1. 尝试数字相加 -> nb_add
PyObject *result = binary_op1(v, w, NB_SLOT(nb_add));
// 2. 尝试序列相拼 -> sq_concat
if (result == Py_NotImplemented) {
PySequenceMethods *m = Py_TYPE(v)->tp_as_sequence;
Py_DECREF(result);
if (m && m->sq_concat) {
return (*m->sq_concat)(v, w);
}
result = binop_type_error(v, w, "+");
}
return result;
}
binary_op1
进一步追踪 binary_op1() —— 这一步主要决策调用 a 还是 b 的 nb_add
static PyObject *
binary_op1(PyObject *v, PyObject *w, const int op_slot)
{
PyObject *x;
binaryfunc slotv = NULL;
binaryfunc slotw = NULL;
// 假设执行 a + b:
// v = a,w = b,op_slot 用于定位具体操作(例如 nb_add)。
// a 和 b 类型相同 -> slotv = type(a) 的 nb_add
if (Py_TYPE(v)->tp_as_number != NULL)
slotv = NB_BINOP(Py_TYPE(v)->tp_as_number, op_slot);
// a 和 b 类型不同 -> slotw = type(b) 的 nb_add
if (!Py_IS_TYPE(w, Py_TYPE(v)) &&
Py_TYPE(w)->tp_as_number != NULL) {
slotw = NB_BINOP(Py_TYPE(w)->tp_as_number, op_slot);
if (slotw == slotv)
slotw = NULL;
}
if (slotv) {
// 1. 如果 b 的类型是 a 类型的子类,优先调用子类 b 的实现
if (slotw && PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v))) {
x = slotw(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x); /* can't do it */
slotw = NULL;
}
// 2. 默认尝试左操作数 a 的实现。
x = slotv(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x); /* can't do it */
}
// 3. 尝试右操作数 b 的 slot(i.e. a 没有实现 slot)
if (slotw) {
x = slotw(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x); /* can't do it */
}
Py_RETURN_NOTIMPLEMENTED;
}
nb_add
两种定义定义/创建「类型」的方式,决定了最终不同的底层实现:
1)静态定义的类型
例如内置类型 float:
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
...
&float_as_number, /* tp_as_number */
...
};
通过 tp_as_number 变量定义加减乘除操作:
static PyNumberMethods float_as_number = {
float_add, /* nb_add */
float_sub, /* nb_subtract */
float_mul, /* nb_multiply */
// ... more number slots
};
nb_add 的具体实现:
static PyObject *
float_add(PyObject *v, PyObject *w)
{
double a,b;
CONVERT_TO_DOUBLE(v, a);
CONVERT_TO_DOUBLE(w, b);
a = a + b;
return PyFloat_FromDouble(a);
}
2)动态创建的类型
参考以前的两篇文章:
我们都知道一个 instance 由 python class 创建,而一个 class 本身则是由 metaclass 实例化得到。当我们定义 class A: ... 只需要一行代码,是因为 cpython 提前定义了一个内置的:PyType_Type —— 也就是 metaclass type 的底层实现!
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
...
// type()
(ternaryfunc)type_call, /* tp_call */
type_init, /* tp_init */
type_new, /* tp_new */
...
};
具体步骤:
class A: ... -> builtins.__build_class__() -> type(name, bases, namespace)
type()
-> PyType_Type.tp_call
-> PyType_Type.tp_new // create new class object (PyHeapTypeObject)
-> PyType_Type.tp_init
-> class A
魔术方法
了解 class 定义创建的原理后,让我们继续探索最后一个问题:魔术方法 __add__ 与底层的 slot nb_add 到底是如何关联的?
class A:
def __add__(self, other):
...
简而言之,cpython 在,维护了一张映射表:
- 定义阶段:
A.__add__ -> slotdefs -> nb_add = slot_nb_add - 运行阶段:
a + b -> nb_add -> slot_nb_add (A.__add__)
#define TPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
{NAME, offsetof(PyTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
PyDoc_STR(DOC)}
static slotdef slotdefs[] = {
// ...
BINSLOT("__add__", nb_add, slot_nb_add, "+"),
RBINSLOT("__radd__", nb_add, slot_nb_add,"+"),
// ...
}
总结
a + b
-> BINARY_ADD
-> PyNumber_Add(a, b)
-> binary_op1(a, b, NB_SLOT(nb_add))
^ slot offset
-> Py_TYPE(a/b) -> PyTypeObject.tp_as_number + offset -> nb_add
-> float_add // built-in type
or
-> __add__() // user-defined class
Read more
- 读 Python behind the scenes #6: how Python object system works
- 读 Python behind the scenes #5: how variables are implemented in CPython
- 读 Python behind the scenes #4: how Python bytecode is executed
- 读 Python behind the scenes #3: stepping through the CPython source code
- 读 Python behind the scenes #2: how the CPython compiler works
- 读 Python behind the scenes #1: how the CPython VM works