【CPython3.6源码分析】Python 自定义类

参考

前言

在上一讲,我们谈到了内置类的初始化工作,其中最主要的逻辑落在 PyType_Ready 中。本讲,我们将主要查看用户自定义类及实例化,在 Python 中的实现过程。

自定义类

1
2
3
4
5
6
7
8
9
10
11
12
class A:
name = 'a'
def __init__(self):
print("A:__init__")
def f(self):
print("A:f")
def g(self, v):
self.v = v
print(self.v)
a = A()
a.f()
a.g(10)

从编译的字节码可以发现,创建类、实例化,以及调用实例的方法,都是通过CALL_FUNCTION实现。在Python 函数机制中,我们已经分析过 CALL_FUNCTION 的实现:”通过栈中的 func 指针及参数,在加上当前的 global 命名空间,创建一个 Frame 对象。并在新的 Frame 中加载 Func.code 执行字节码指令,最终将返回值压入栈中。”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 1           0 LOAD_BUILD_CLASS
2 LOAD_CONST 0 (<code object A >)
4 LOAD_CONST 1 ('A')
6 MAKE_FUNCTION 0
8 LOAD_CONST 1 ('A')
10 CALL_FUNCTION 2 // 创建类
12 STORE_NAME 0 (A)

10 14 LOAD_NAME 0 (A)
16 CALL_FUNCTION 0
18 STORE_NAME 1 (a)
11 20 LOAD_NAME 1 (a)
22 LOAD_ATTR 2 (f)
24 CALL_FUNCTION 0
26 POP_TOP
12 28 LOAD_NAME 1 (a)
30 LOAD_ATTR 3 (g)
32 LOAD_CONST 2 (10)
34 CALL_FUNCTION 1
36 POP_TOP
38 LOAD_CONST 3 (None)
40 RETURN_VALUE

那么我们先看下 demo.py 对应的CodeObject。

CodeObject:demo.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
1 0 LOAD_BUILD_CLASS
2 LOAD_CONST 0 (<code object A >)
4 LOAD_CONST 1 ('A')
6 MAKE_FUNCTION 0
8 LOAD_CONST 1 ('A')
10 CALL_FUNCTION 2
12 STORE_NAME 0 (A)

/*
opcode:: LOAD_BUILD_CLASS
Pushes :func:`builtins.__build_class__` onto the stack.
It is later called by :opcode:`CALL_FUNCTION` to construct a class.
*/

TARGET(LOAD_BUILD_CLASS) {
PyObject *bc;
/* 从 f->f_builtins 中获取 __build_class__
bc = __builtins__['__build_class__']
PUSH(bc);
*/
}

跟函数机制类似:

  • 0,加载 __build_class__ 方法到栈顶
  • 2、4、6,加载 <code object A > 函数名 A,创建一个 FuncObject 入栈
  • 8、10,从栈中弹出 build_class、FuncObject、参数,创建一个新的 Frame 对象,加载字节码执行
  • 12 将栈顶的结果转移到 local 命名空间中

CALL_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
TARGET(CALL_FUNCTION) {
sp = stack_pointer;
res = call_function(&sp, oparg, NULL); // oparg == 2
}

static PyObject *
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames){
PyObject *func = *((*pp_stack) - oparg - 1);
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
Py_ssize_t nargs = oparg - nkwargs;

if (PyCFunction_Check(func)) {
stack = (*pp_stack) - nargs - nkwargs; // - 2 - 0
x = _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames);
}
return x;
}

PyObject *
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack,
Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *kwdict, *result;
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);

if (nkwargs > 0) {
/* zip 组合成字典,说明 kwnames 必须为 str 且唯一 */
kwdict = _PyStack_AsDict(stack + nargs, kwnames);
}
else {
kwdict = NULL;
}
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
Py_XDECREF(kwdict);
return result;
}

特别注意:10 CALL_FUNCTION 2,参数个数为2。执行前的栈帧结构如下:

1
2
3
__builtins__['__build_class__']
FuncObject
CONST:"A"

因此,最终调用时的参数为:

1
2
3
4
5
6
7
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
/*
func = __builtins__['__build_class__']
stack = &FuncObject
nargs = 2
kwdict = NULL
*/

_PyCFunction_FastCallDict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// methodobject.c.153
PyObject *
_PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
PyObject *kwargs)
{
PyCFunctionObject *func = (PyCFunctionObject*)func_obj;
PyObject *result;
int flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
switch (flags)
{
case METH_NOARGS:
... break;
case METH_O:
... break;
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
... break;
case METH_FASTCALL:
... break;
default
return NULL;
}
return result;
}

// bltinmodule.c.2635
static PyMethodDef builtin_methods[] = {
{"__build_class__", (PyCFunction)builtin___build_class__,
METH_VARARGS | METH_KEYWORDS, build_class_doc},
}

// methodobject.h.91
typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
PyObject *m_module; /* The __module__ attribute, can be anything */
PyObject *m_weakreflist; /* List of weak references */
} PyCFunctionObject;

struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __daoc__ attribute, or NULL */
} PyMethodDef;

回想上一节谈到的 Python 类机制,在 PyType_Ready 中会经历一步 add_methods,把一个 C 函数封装成 Python 对象,放入字典中。此处,也不例外。根据 flags,程序将执行下面的分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PyCFunction meth = func -> m_ml -> ml_meth;
PyObject *self = func -> m_self; // == NULL
case METH_VARARGS | METH_KEYWORDS:
{
PyObject *tuple;
tuple = _PyStack_AsTuple(args, nargs);
result = (*(PyCFunctionWithKeywords)meth) (self, tuple, kwargs);
Py_DECREF(tuple);
break;
}
/*
meth = builtin___build_class__
self = NULL
tuple = (FuncObject,"A")
kwargs = NULL
*/

此处,将直接执行 C 函数builtin___build_class__(NULL, (FuncObject, "A"), NULL)

build_class

PEP3115

在查看源码前,我们先来看下PEP3115。该提案改变了 Python3 中 metaclass 的定义方式。按照 Guido 所说,class 语句将接受关键字参数,并且为元类增加 __prepare__ 方法逻辑。在用元类创建类之前,会先尝试调用元类的__prepare__方法,返回一个字典,作为局部变量字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class C(A, B, metaclass=M, other=42, *more_bases, *more_kwds):
...
# would translate into this:
C = __build_class__(<func>, 'C', A, B, metaclass=M, other=42,
*more_bases, *more_kwds)

# Then __build_class__ could be roughly like this (but implemented in C):
def __build_class__(func, name, *bases, metaclass=None, **kwds):
if metaclass is None:
metaclass = extract_metaclass(bases) # may raise an exception
prepare = getattr(metaclass, "__prepare__", None)
if prepare:
locals = prepare(name, bases, **kwds)
else:
locals = {}
func(locals)
return metaclass(name, bases, locals, **kwds)

builtin___build_class__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// bltinmodule.c.55
static PyObject *
builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds){
assert args is tuple
assert nargs = len(args) >= 2
asert func = args[0] is callable
asert name = args[1] is string
bases = args[2:]
if (kwds == NULL):
meta = NULL
mkw = NULL
else:
mkw = kwds.copy()
meta = mkw.pop('metaclass', NULL)
if meta is NULL:
if not bases:
meta = PyType_Typ
else:
meta = bases[0].__calss__
if isinstance(meta, type):
meta = 更深层的 meta

ns = meta.__prepare__() or {} // namespace 的简写
/* 将 class block 存储到 ns 中 */
cell = PyEval_EvalCodeEx(func.__code__
func.__global__, ns,
NULL, 0, NULL, 0, NULL, 0, NULL,
func.closure);

// 创建类
PyObject *cls = NULL;
if cell != NULL:
PyObject *margs[3] = {name, bases, ns};
// 相当于执行 meta(name_str, bases_tuple, namespace_dict)
cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
return cls
}

注意,上面不伦不类的伪代码是笔者根据源码改写的,具体内容请查看bltinmodule.c。可以发现在此处真正完成了类的创建。

特别注意,此处传入的 CodeObject 是从栈中取出的 FuncObject.code,即对应代码中的<code object A>

PyEval_EvalCodeEx 将调用 _PyEval_EvalCodeWithName。后者,我们在 Python函数机制 中已经谈到,最终都是创建栈帧对象,在新的上下文环境执行被调函数字节码指令。那么接下来就是在新的 Frame 中,执行 class A 的字节码指令,等待返回,执行创建类的代码

CodeObject:Class A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// <code object A>
co_consts = ('A', 'a', <code object __init__ >, 'A.__init__',
<code object f>, 'A.f', <code object g>, 'A.g', None)

co_names = ('__name__', '__module__', '__qualname__', 'name',
'__init__', 'f', 'g')

1 0 LOAD_NAME 0 (__name__)
2 STORE_NAME 1 (__module__)
4 LOAD_CONST 0 ('A')
6 STORE_NAME 2 (__qualname__)
2 8 LOAD_CONST 1 ('a')
10 STORE_NAME 3 (name)
3 12 LOAD_CONST 2 (<code object __init__>)
14 LOAD_CONST 3 ('A.__init__')
16 MAKE_FUNCTION 0
18 STORE_NAME 4 (__init__)
5 20 LOAD_CONST 4 (<code object f>)
22 LOAD_CONST 5 ('A.f')
24 MAKE_FUNCTION 0
26 STORE_NAME 5 (f)
7 28 LOAD_CONST 6 (<code object g>)
30 LOAD_CONST 7 ('A.g')
32 MAKE_FUNCTION 0
34 STORE_NAME 6 (g)
36 LOAD_CONST 8 (None)
38 RETURN_VALUE

如上,在新的 Frame 中,执行 CodeObject class A,整体上都在加载变量,创建 Function,存储到此时的 locals 命名空间中。要注意到,此时的 locals,是在__build_class__中,创建并传入的ns。意思就是,这些属性、方法,都存放在了 ns 中。最后,返回 None,回到__build_class__继续执行,跳转到 builtin___build_class__

_PyObject_FastCallDict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PyObject *margs[3] = {name, bases, ns};
cls = _PyObject_FastCallDict(meta, margs, 3, mkw);

PyObject *
_PyObject_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
PyObject *kwargs)
{
ternaryfunc call;
PyObject *result = NULL;
PyObject *tuple = &_PyStack_AsTuple(args, nargs);
call = func->ob_type->tp_call;
if (call == NULL) {
PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
func->ob_type->tp_name);
goto exit;
}
result = (*call)(func, tuple, kwargs);
return result;
}

PyTypeObject PyType_Type = {
(ternaryfunc)type_call, /* tp_call */
type_new, /* tp_new */
type_init, /* tp_init */
}

简写后的代码如上,此处的 func,就是上文传入的 metaclass,默认为 PyType_Type。那么就将调用 type_call(),创建类。

这里就要提一句,为什么传入一个对象,却可以作为 func 使用?在 Python 中,函数是可以被调用的。而函数是
对象,变量也是对象。那么为什么函数可以被调用,而变量却不能?

事实上,源代码已经告诉了我们答案:object->ob_type->tp_call,只要 tp_call 存在且是函数。该对象就是可以被调用的。

1
2
3
4
5
6
7
8
9
10
>>> class Ca:
... def __call__(self):
... print("call: ca")
...
>>> Ca.__class__.__call__
<slot wrapper '__call__' of 'type' objects>
>>> callable(Ca())
True
>>> Ca().__class__.__call__
<function Ca.__call__ at 0x000001C8B28BEA60>

执行 classObject(),就是执行 PyType_Type.tp_call()。执行 instance(),就是执行instance.__class__.__call__

type_call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj;
obj = type->tp_new(type, args, kwds);
obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);

/* Ugly exception: when the call was type(something),
don't call tp_init on the result. */
if (type == &PyType_Type &&
PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
(kwds == NULL ||
(PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
return obj;

/* If the returned object is not an instance of type,
it won't be initialized. */
if (!PyType_IsSubtype(Py_TYPE(obj), type))
return obj;

type = Py_TYPE(obj);
if (type->tp_init != NULL) {
type->tp_init(obj, args, kwds);
}
return obj;
}

调用 type->type_new(),得到一个 classObject。判断,是否执行的是 type(classObject),再判断 classObject 是否继承自 type,否则进入我们熟悉的环节__init__

type_new()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class A(list):
... name = 'n'
... def f(self):
... print(self)
...
>>> B = type('B', (list,), {'name': 'n', "f": lambda self: print(self)})
>>> type(B) is type
True
>>> A.name
'n'
>>> B.name
'n'
>>> type('C',())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments

在查看 type_new 之前,先了解下 Python 中类的创建。从上面可以发现,type() 接收1个参数时,返回类型对象,3个参数时创建类。好了,了解之后就开始进入 type_new 环节。

1
2
3
4
5
6
7
8
9
// typeobject.c.2285-2731
static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds){
/*
metatype = type
args = (类名 name, 基类 bases, 命名空间 ns)
kwds = 附加参数 or {}
*/
}

type_new是一个非常复杂的函数,有超过400行。它在 Python 中扮演着非常重要的角色,正是由它创建了 Python 的各种 class。

type(x)

1
2
3
4
5
6
7
8
9
10
11
12
if (metatype == &PyType_Type) {
const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds);

/* Special case: type(x) should return x->ob_type */
/* We only want type itself to accept the one-argument form (#27157)
Note: We don't call PyType_CheckExact as that also allows subclasses */
if (nargs == 1 && nkwds == 0) {
PyObject *x = PyTuple_GET_ITEM(args, 0);
Py_INCREF(Py_TYPE(x));
return (PyObject *) Py_TYPE(x);
}

实际在调用时,type 本身也是作为形参 metatype 传入的。判断参数个数,若参数只有1个,那么就实在执行type(classObject),直接返回type(args[0])

确定 metatype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Determine the proper metatype to deal with this: */
winner = _PyType_CalculateMetaclass(metatype, bases);
if (winner == NULL) {
return NULL;
}

if (winner != metatype) {
if (winner->tp_new != type_new) /* Pass it to the winner */
return winner->tp_new(winner, args, kwds);
metatype = winner;
}

/* Adjust for empty tuple bases */
nbases = PyTuple_GET_SIZE(bases);
if (nbases == 0) {
/* 这就是 为什么 Python3 中自动继承 object */
bases = PyTuple_Pack(1, &PyBaseObject_Type);
if (bases == NULL)
goto error;
nbases = 1;
}

找出最佳的 metaclass,并处理 base=() 的情况。

slot

1
2
3
4
/* Check for a __slots__ sequence variable in dict, and count it */
slots = _PyDict_GetItemId(di#$ct, &PyId___slots__);
if (slots == NULL) { ... }
else { ... }

__slot__属性,允许我们显式的指定成员属性,经常用于存在大量实例的情况下减小内存占用。在这种情况下,将不会存在__dict__属性。具体参考:python.org

Allocate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 /* Allocate the type object, 不使用 __slot__时 nslots=0 */
type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);

/* Keep name and slots alive in the extended type object */
et = (PyHeapTypeObject *)type;
et->ht_name = name;
et->ht_slots = slots;

/* Initialize tp_flags */
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_FINALIZE;
if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
type->tp_flags |= Py_TPFLAGS_HAVE_GC;

/* Initialize essential fields */
type->tp_as_async = &et->as_async;
type->tp_as_number = &et->as_number;
type->tp_as_sequence = &et->as_sequence;
type->tp_as_mapping = &et->as_mapping;
type->tp_as_buffer = &et->as_buffer;
type->tp_name = PyUnicode_AsUTF8AndSize(name, &name_size);

/* Set tp_base and tp_bases */
type->tp_bases = bases;
type->tp_base = base;
/* Initialize tp_dict from passed-in dict */
type->tp_dict = dict;
/* Set __module__ in the dict */
/* Set ht_qualname to dict['__qualname__'] if available, else to
__name__. The __qualname__ accessor will look for ht_qualname.
*/
/* Set tp_doc to a copy of dict['__doc__'], if the latter is there
and is a string. The __doc__ accessor will first look for tp_doc;
if that fails, it will still look into __dict__.
*/
/* Special-case __new__: if it's a plain function,
make it a static function */
/* Special-case __init_subclass__: if it's a plain function,
make it a classmethod */
/* Add descriptors for custom slots from __slots__, or for __dict__ */
/* Special case some slots */

如上,在这部分调用 tp_alloc 在堆上分配内存。然后进行了一些属性和方法的初始化工作。

这里要注意一点,若查看 PyType_Type 源码会发现 tp_alloc 为0,这可怎么搞?
还记得 Python类机制中谈到,PyType_Ready 会将所有的 type.base 默认设置为 object。
因此,PyType_Type 将继承到 PyBaseObject_Type->PyType_GenericAlloc 方法。

Gc&Ready

1
2
3
4
5
6
7
8
9
10
11
12
/* Enable GC unless this class is not adding new instance variables and
the base class did not use GC. */
/* Always override allocation strategy to use regular heap */
/* store type in class' cell if one is supplied */
/* Initialize the rest */
if (PyType_Ready(type) < 0)
goto error;

/* Put the proper slots in place */
fixup_slot_dispatchers(type);

return (PyObject *)type;

最终,调用 PyType_Ready 对用户创建的类进行初始化,完成后返回 type_call
此时,判断满足条件PyType_IsSubtype(Py_TYPE(obj), type),直接返回 classObject,完成创建类的工作。

特别注意:

  1. PyType_Ready(class A) 会为 A 继承一些属性/方法,其中就包括 tp_new/tp_init。
  2. fixup_slot_dispatchers会替换存在于 slotdefs中,且用户自定义的魔术方法。

实例化自定义类

1
2
3
4
a = A()
10 14 LOAD_NAME 0 (A)
16 CALL_FUNCTION 0
18 STORE_NAME 1 (a)

可见,创建实例对象,依此调用CALL_FUNCTION->call_function->_PyObject_FastCallKeywords->_PyObject_FastCallDict最终找到func->ob_type->tp_call,执行tp_call()

这又回到了创建自定义类的套路,问题在于,此时的 tp_new/tp_init 还是之前的 PyType_Type->tp_new吗?

回顾 PyType_Ready,Python3 默认添加基类 object,若用户没有自定义,那么 tp_new 自然是PyBaseObject_Type->tp_new 即 object_new 。

object_new

1
2
3
4
5
6
7
8
9
10
static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
/* 处理含参数,但 type->tp_init == object_init,报错 */
/* 处理抽象类 */
if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
...
}
return type->tp_alloc(type, 0);
}

是不是非常清晰、明了,比 type->type_new 的400行代码,看着舒服多了~完成后同样返回到 type_call
此时,不满足条件PyType_IsSubtype(Py_TYPE(obj), type),继续执行tp_init(obj, args, kwds),完成实例的初始化工作。

访问实例属性

1
2
3
4
5
a.f()
11 20 LOAD_NAME 1 (a)
22 LOAD_ATTR 2 (f)
24 CALL_FUNCTION 0
26 POP_TOP

很明显,其中LOAD_ATTR将发挥重要作用。

LOAD_ATTR

1
2
3
4
5
6
7
8
9
10
11
12
13
opcode:: LOAD_ATTR (namei)
Replaces TOS with ``getattr(TOS, co_names[namei])``.

TARGET(LOAD_ATTR) {
PyObject *name = GETITEM(names, oparg);
PyObject *owner = TOP();
PyObject *res = PyObject_GetAttr(owner, name);
Py_DECREF(owner);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}

恩,看注释就很好理解。那么在来看PyObject_GetAttr。在查看源码前,先了解下 Python 中的描述符协议

描述符协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
>>> class Field:
... """ 实现描述符协议的类,描述符类 """
... def __init__(self, name):
... self.name = name
... def __set__(self, instance, value):
... """
... self 为描述符实例
... instance 为托管类的实例
... 为 托管实例的托管属性进行赋值时,调用。
...
... 描述符协议,是对托管实例,进行操作。
... """
... print("use __set__")
... instance.__dict__[self.name] = value
... def __get__(self, instance, owner):
... """
... self 为描述类实例
... instance 为托管类实例
... owner 为托管类
... """
... print("use __get__")
... return instance.__dict__[self.name]
...
>>> class Model:
... """ 把描述符实例,声明为类属性的类,托管类 """
... width = Field('width') # 描述符实例 Field()
... height = Field("height") # 描述符属性 height,都是类属性
... def __init__(self, width, height):
... """ 由描述符协议进行托管的,托管属性 """
... self.width = width
... self.height = height
...
>>> m = Model(10, 32)
use __set__
use __set__
>>> m.__dict__
{'width': 10, 'height': 32}

写过框架或者熟悉ORM的人,应该对其不陌生。描述符,在ORM中经常被用于数据验证。实际上,描述符在 Python 内部被大量使用。属性访问得到的对象,如果包含描述符方法,会调用该方法并返回。

描述符定义为,对象包含以下任意方法,__get__(), __set__(), and __delete__()的对象。对实例来说,获取属性 x ,会lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], 并且继续在 bases 链中寻找,但不包括元类。

但是,如果查找的值是一个定义描述符方法的对象,那么Python可以重写默认行为并调用描述符方法。在优先级链中发生这种情况取决于定义了哪些描述符方法以及如何调用它们。

描述符优先级

描述符调用的优先级取决于定义了哪些描述符方法:

  • 如果它没有定义 get(),那么访问该属性将返回描述符对象本身,除非对象的实例字典中有一个值。
  • 如果描述符定义了 set()或del() 那么它就是一个数据描述符
  • 如果它既不定义set()也不定义del(),那么它就是一个非数据描述符
  • 通常,数据描述符定义get()和set(),而非数据描述符只有get()方法。

总体上就是:数据描述符 > 实例覆盖> 非数据描述符

Python 方法(包括staticmethod()和classmethod()))实现为非数据描述符。因此,实例可以重新定义和覆盖方法。这允许实例获得与其他实例不同的行为。
属性方法 property() 函数实现为数据描述符。因此,实例不能覆盖属性的行为。

因此,会有以下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> class A:
... @staticmethod
... def s():
... print("非数据描述符")
... @classmethod
... def c(cls):
... print("非数据描述符")
... @property
... def p(self):
... return "数据描述符"
...
>>> a = A()
>>> a.s()
非数据描述符
>>> a.c = 1
>>> a.c
1
>>> a.__dict__
{'c': 1}
>>> a.p
'数据描述符'
>>> a.p = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> a.__dict__['p'] = 2
>>> a.__dict__
{'c': 1, 'p': 2}
>>> a.p
'数据描述符'

好了,如果还能保持清醒,那么就继续看PyObject_GetAttr的源码吧!

PyObject_GetAttr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// objectc.c.884
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(v);
assert PyUnicode_Check(name)

if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
if (tp->tp_getattr != NULL) {
char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL)
return NULL;
return (*tp->tp_getattr)(v, name_str);
}
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
return NULL;
}

如上,tp_getattr 在内部已经被弃用,首选通过 tp_getattro,即PyBaseObject_Type->PyObject_GenericGetAttr,获取属性。

PyObject_GenericGetAttr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
}
/*
Generic attribute getter function that is meant to be put into
a type object’s tp_getattro slot.
It looks for a descriptor in the dictionary of classes in the
object’s MRO as well as an attribute in the object’s __dict__
(if present).
As outlined in Implementing Descriptors, data descriptors take
preference over instance attributes, while non-data descriptors
don’t.
Otherwise, an AttributeError is raised.
*/

从注释中,可以获取到,通用属性搜索在 typeObject->tp_getattro 槽中。会在 MRO 及__dict__中寻找描述符。数据描述符优先于实例属性,未找到会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{
PyTypeObject *tp = Py_TYPE(obj);
PyObject *descr = NULL;
PyObject *res = NULL;
descrgetfunc f;
Py_ssize_t dictoffset;
PyObject **dictptr;

if (tp->tp_dict == NULL) {
if (PyType_Ready(tp) < 0)
goto done;
}
/* Look for a name through the MRO */
descr = _PyType_Lookup(tp, name);

/* 尝试在 instance.__class__.__mro__ 链中寻找描述符 */
f = NULL;
if (descr != NULL) {
Py_INCREF(descr);
f = descr->ob_type->tp_descr_get;
if (f != NULL && PyDescr_IsData(descr)) {
/* 数据描述符,调用 __get__(self, instance, owner) */
res = f(descr, obj, (PyObject *)obj->ob_type);
goto done;
}
}

/* 获取 __dict__ */
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
// 变长对象 dictoffset 负处理
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}
}

/* 尝试在 instance.__dict__ 中寻找 */
if (dict != NULL) {
Py_INCREF(dict);
res = PyDict_GetItem(dict, name);
if (res != NULL) {
Py_INCREF(res);
Py_DECREF(dict);
goto done;
}
Py_DECREF(dict);
}
/* 处理找到描述符,但不是数据描述符 */
if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
goto done;
}

/* 处理,找到描述符,但无 __get__ 方法,返回描述符 */
if (descr != NULL) {
res = descr;
descr = NULL;
goto done;
}
/* 什么都没找到,最终报错 */
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
done:
Py_XDECREF(descr);
Py_DECREF(name);
return res;
}

如上,具体逻辑跟描述符优先级中的内容一模一样,沿着优先级一步一步尝试获取。

小结

至此,我们通过LOAD_BUILD_CLASS + CALL_FUNCTION指令,进入到 C 函数 builtin___build_class__(NULL, (FuncObject, "A"), NULL)
在其中通过PyEval_EvalCodeEx将 FuncObject 执行后存储到 ns 中,然后进入到meta(name_str, bases_tuple, namespace_dict)
最终我们进入PyType_Type->tp_new,完成类的内存分配、初始化,创建类并返回。

通过LOAD_NAME + CALL_FUNCTION,进入 C 函数PyBaseObject_Type->tp_new,完成实例的创建,通过classObject.__init__完成实例的初始化。

通过LOAD_NAME + LOAD_ATTR,进入PyObject_GetAttr,沿着属性访问优先级链描述符 + mro + __dict__,依此尝试获取属性。