《Python 源码剖析》一些理解以及勘误笔记(2)
以下是本人阅读此书时理解的一些笔记,包含一些影响文义的笔误修正,当然不一定正确,贴出来一起讨论。
注:此书剖析的源码是2.5版本,在python.org 可以找到源码。纸质书阅读,pdf 贴图。
文章篇幅太长,故切分成3部分,这是第二部分。
p248: 嵌套函数、闭包和 decorator
co_cellvars: 通常是一个tuple,保存嵌套的作用域内使用的变量名集合;
co_freevars: 通常是一个tuple,保存使用了的外层作用域中的变量名集合。
如下的一段Python 代码:
2 3 4 5 6 7 8 |
def get_func():
value = "inner" def inner_func(): print value return inner_func show_value = get_func() show_value() |
则py 文件编译出来的PyCodeObject 有3个,那么与get_func 对应的对象中的 co_cellvars 就应该包含字符串 "value",而与 inner_func
对应的PyCodeObject 对象的co_freevars 也应该有字符串"value"。
闭包从创建、传递到使用的全过程可以用以下三幅图演示:
在inner_func 调用过程中,当引用外层作用域符号时,一定是到 f_localsplus 中的 free 变量区域获取符号对应的值。
在closure 技术的基础上,Python 实现了 decorator。
p264: Python 中的可调用性(callable)。只要一个对象对应的class 对象中实现了"__call__" 操作(更确切地说,在 Python 内部的
PyTypeObject 中,tp_call 不为空),那么这个对象就是一个可调用的对象,比如:
class A(object):
def __call__(self): print ‘Hello Python‘
那么 a= A() a() 会输出‘Hello Python‘ ;可以认为 PyA_Type 对象的 tp_call 不为空。在 c++ 看来也就是函数对象的实现。
p268: PyTypeObject 的tp_dict 填充、descriptor
在Python 内部,存在多种 descriptor,PyType_Ready 在通过add_operators 添加了 PyTypeObject 对象中定义的一些 operator 后,
还会通过 add_methods、add_members、add_getsets 添加在PyType_Object 中定义的 tp_methods、tp_members、tp_getset 函数
集。这些 add_*** 的过程与 add_operator 类似,不过最后添加到 tp_dict 中的descriptor 就不再是PyWrapperDescrObject,而分别是
PyMethodDescrObject、PyMemberDescrObject、PyGetSetDescrObject。
注:PyWrapperDescrObject 的 type 是 PyWrapperDescr_Type,PyWrapperDescr_Type 对象中的 tp_call 是wrapperdescr_call,当
Python 虚拟机”调用“一个 descriptor 时,也就会调用 wrapperdescr_call 。
一般而言,对于一个 Python 中的对象obj,如果 obj.__class__ 对应的 class 对象中存在 __get__ 、__set__、__delete__ 三种操作,那么 obj 可以称
为Python 的一个 descriptor。像 PyWrapperDescr_Type 的 tp_descr_get 设置了 wrapperdescr_get,故称 PyWrapperDescrObject 为 descriptor。
如上图来说,实际上 mp_subscript 和 d_wrapped 都是函数指针变量,它们的值相等,都是 list_subscript 。
如下的例子重写了list 的 ‘__repr__ ‘ 方法,则初始化完成后的 A 如下图所示:
class A(list):
def __repr__(self): return ‘Python‘
即 tp_repr 的值本来与 d_wrapped 一样,都是 list_repr,创建时被替换为 slot_to_repr。在 slot_to_repr 中,会寻找 ‘__repr__‘ 方法对
应的 PyFunctionObject 对象,正好就找到在 A 定义中重写的函数。
所谓的MRO 即 Method Resolve Order,也是一个class 对象的属性解析顺序(继承情况下),class A(list) class B(list) class C(A) class D(C, B)
则 D 的 mro 列表 是(D, C, A, B, list),保存在 PyTypeObject.tp_mro 中,可以访问 type.__mro__ 获取。
基类中的子类列表保存在 PyTypeObject.tp_subclasses 中,比如访问 int.__subclasses__() 是 <type ‘bool‘>。
Python 虚拟机在 PyType_Ready 中对各种 PyTypeObject 对象进行了复杂的改造动作,包括如下:
1). 设置 ob_type 信息,tp_base 信息(指向一个 PyTupleObject 对象,即基类列表);
2). 填充 tp_dict; 3). 确定 mro 列表在 tp_mro; 4). 基于mro 列表从基类继承属性操作;5). 设置基类的子类列表在 tp_subclasses 。
p286: 在创建类对应的 PyFrameObject 对象时,f_locals 被创建并指向一个PyDictObject 对象,包含class 变量和成员函数对象,在 Frame 回退
时 f_locals 先会被压入到运行时栈后被弹出给前一个 Frame 的运行时栈,并作为创建 class 对象的其中一个属性表参数,这个参数一般还包含
{‘__module__‘ : ‘__main__‘, ‘__doc__‘ : None}。
而在函数机制中,f_locals 被设置为NULL,函数机制中的局部变量是以一种位置参数的形式放在了运行时栈前面的那段内存中。
p292: slotoffset = base->basicsize; basicsize 修改为 tp_basicsize 。
p283: 创建 class 对象、创建 instance 对象
PyIntObject、PyDictObject 等对象是Python 静态提供的,它们都具有相同的接口集合,当然有的对象可能不支持某个接口,但这并
不影响它们的所有元信息全存储在其类型对象 PyType_Type 中;而用户自定义的class 对象A,其接口是动态的,不可能在
metaclass 中静态地指定,故在利用PyType_Type 对象创建 用户自定义 class 对象A 时还需要传递 (classname, bases 基类列表,
methods 属性表[class 变量、成员函数])。
因为PyType_Type 实现了 tp_call,故我们说‘调用‘ PyType_Type 创建一个自定义class 对象,流程是
call_function --> do_call --> PyObject_Call --> tp_call(type_call) --> tp_new(type_new) --> tp_alloc,
tp_alloc 继承自<type ‘object‘> 的 tp_alloc 即 PyType_GenericAlloc,最终申请的内存大小是
metatype->tp_basicsize + metatype->tp_itemsize,从 typeobject.c 中的定义可以看到实际上就是
sizeof(PyHeapTypeObject) + sizeof(PyMemberDef)。
而 Atype->tp_basicsize = base->tp_basicsize + 8; Atype->tp_itemsize = base->itemsize; 其中 8 是 2*sizeof(PyObject*)
Atype->tp_dictoffset = base->tp_basicsize; Atype->tp_weaklistoffset = base->tp_basicsize + 4;
如果自定义 class 重写了 __new__, 将__new__ 对应的函数改造为 static method; Atype->tp_dict 设置为 methods 属性dict ; 调用 PyType_Ready
对 class 对象进行初始化。
当通过 a=A() 这样的表达式创建instance 对象时,即‘调用’ class 对象将创建 instance 对象,同样沿用上面的调用路径,但
PyType_Type.tp_call 中调用的是A.tp_new,A.tp_new 继承自object.tp_new,在PyBaseObject_Type 中定义为object_new。
在object_new 中,调用了A.tp_alloc,这个操作也是继承自object,即PyType_GenericAlloc。由上面分析可知,申请的内存大小为
A.tp_basicsize + A.tp_itemsize(0) ,申请完内存空间回到 type_call,由于创建的不是 class 对象而是 instance 对象,会尝试调用
AType->tp_init 进行 instance 初始化。一般情况下我们会重写 class A 的 ‘__init__‘ 方法,故 tp_init 不再是在 PyType_Ready 中继承
的PyBaseObject_Type 的 object_init 操作,而是 slot_tp_init。
p296: 访问和设置 instance 对象中的属性
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class A(object):
name = "Python" def __init__(self): print "A::__init__" def f(self): print "A::f" def g(self, aValue): self.value = aValue print self.Value a = A() a.f() a.g(10) |
在 Python 中,形如 x.y or x.y() 形式的表达式称为“属性引用”,属性可能是简单的数据或者成员函数。PyObject_GetAttr 中调用 Atype->tp_getattro
操作,是继承自 PyBaseObject_Type 的 PyObject_GenericGetAttr、对应的还有 PyObject_GenericSetAttr,下面是简略版函数实现。
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
// a = A() a.f() // object.c
// obj=&a name = "f" PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { PyTypeObject *tp = obj->ob_type; // A 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; } // 在 A 的 mro 列表中的基类对象的 tp_dict 中查找 ‘f‘ 对应的 descriptor /* Inline _PyType_Lookup */ { Py_ssize_t i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; //tuple n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); dict = ((PyTypeObject *)base)->tp_dict; descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } f = NULL; if (descr != NULL) { f = descr->ob_type->tp_descr_get; // type = descriptor.__class__, __get__ if (f != NULL && descr->ob_type->tp_descr_set != NULL) { // __set__ res = f(descr, obj, (PyObject *)obj->ob_type); goto done; // PyObject_GenericGetAttr 返回 type.__get__(descriptor, a, A) 的执行结果 } } // 如果不是 data descriptor (type 同时定义了__set__ 和 __get__) // 在 a.__dict 中查找 /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { // 说明A 继承自 str 这样的变长对象,对 dictoffset 做些调整 } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { res = PyDict_GetItem(dict, name); // PyObject_GenericGetAttr 返回 a.__dict__[‘f‘] if (res != NULL) { goto done; } } } // 在 a.__dict__ 中没有找到 if (f != NULL) { // __get__ res = f(descr, obj, (PyObject *)obj->ob_type); goto done; // PyObject_GenericGetAttr 返回 type.__get__(descriptor, a, A) 的执行结果 } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; // PyObject_GenericGetAttr 返回 descriptor } PyErr_Format(PyExc_AttributeError, "‘%.50s‘ object has no attribute ‘%.400s‘", tp->tp_name, PyString_AS_STRING(name)); done: return res; } // a.time = 2 // obj=&a, name="time", value=2 int PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value) { PyTypeObject *tp = obj->ob_type; PyObject *descr; descrsetfunc f; PyObject **dictptr; int res = -1; ... if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } descr = _PyType_Lookup(tp, name); // inline function f = NULL; if (descr != NULL) { f = descr->ob_type->tp_descr_set; if (f != NULL) { res = f(descr, obj, value); //PyObject_GenericSetAttr 返回 type.__set__(descriptor, a, 2) 的结果 goto done; } } dictptr = _PyObject_GetDictPtr(obj); // inline function if (dictptr != NULL) { PyObject *dict = *dictptr; if (dict == NULL && value != NULL) { // a.__dict__ 还没创建 dict = PyDict_New(); // if (dict == NULL) goto done; *dictptr = dict; } if (dict != NULL) { if (value == NULL) res = PyDict_DelItem(dict, name); // 删除 else res = PyDict_SetItem(dict, name, value); // 设置 a.__dict__[‘time‘] = 2 goto done; } } if (f != NULL) { res = f(descr, obj, value); //PyObject_GenericSetAttr 返回 type.__set__(descriptor, a, 2) 的结果 goto done; } if (descr == NULL) { PyErr_Format(PyExc_AttributeError, "‘%.100s‘ object has no attribute ‘%.200s‘", tp->tp_name, PyString_AS_STRING(name)); goto done; } PyErr_Format(PyExc_AttributeError, "‘%.50s‘ object attribute ‘%.400s‘ is read-only", tp->tp_name, PyString_AS_STRING(name)); done: return res; } |
首先我们在 _PyObject_GetDictPtr 看到前面提过的 tp_dictoffset 的作用,如下图所示:
注意:获取 Atype.__dict__ 访问的是 Atype->tp_dict; 获取 a.__dict__ 访问的是 上图中的__dict__。
对于 descriptor 可以细分为如下两种:
1). data descriptor: type 中定义了 __get__ 和 __set__ 的 descriptor;
2). non data descriptor: type 中只定义了 __get__ 的 descriptor;
在 Python 虚拟机访问 instance 对象时,descriptor 的一个作用是影响 Python 虚拟机对属性的选择,虽然 PyObject_GenericGetAttr
中对属性的选择线路比较复杂,但最终效果上,可以总结如下两条规则:
1). Python 虚拟机按照 instance 属性、class 属性的顺序选择属性,即 instance 属性 优先于 class 属性;
2). 如果在 class 属性中发现同名的 data descriptor,那么该 descriptor 会优先于 instance 属性;
这两条原则在对属性进行设置时仍然会被严格遵守。
参照函数实现,看下面图示代码的演示结果,注意:2.5 源码被我加了很多调试输出而影响观察,故在 Python 3.3.2 下演示,Python
3.x 输出稍有不同,如 <type ‘str‘> 变成 <class ‘str‘> 。
descriptor 改变返回值 ============ instance 属性优先于 non data descriptor ======= data descriptor 优先于 instance 属性
如果我们在上面 py 文件中加入 ‘print A.name‘ 这样的访问 class 对象的属性的表达式时,在 PyObject_GetAttr 中的 Typetype->tp_getattro 调用的是
type_getattro,而非 PyObject_GenericGetAttr,对应的还有 type_setattro,简略代码如下:
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
// B.value // typeobject.c
/* This is similar to PyObject_GenericGetAttr(), but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ static PyObject * type_getattro(PyTypeObject *type, PyObject *name) { PyTypeObject *metatype = type->ob_type; // PyType_Type PyObject *meta_attribute, *attribute; descrgetfunc meta_get; /* Initialize this type (we‘ll assume the metatype is initialized) */ if (type->tp_dict == NULL) { if (PyType_Ready(type) < 0) return NULL; } /* No readable descriptor found yet */ meta_get = NULL; /* Look for the attribute in the metatype */ meta_attribute = _PyType_Lookup(metatype, name); // PyType_Type if (meta_attribute != NULL) { meta_get = meta_attribute->ob_type->tp_descr_get; if (meta_get != NULL && PyDescr_IsData(meta_attribute)) { /* Data descriptors implement tp_descr_set to intercept * writes. Assume the attribute is not overridden in * type‘s tp_dict (and bases): call the descriptor now. */ return meta_get(meta_attribute, (PyObject *)type, (PyObject *)metatype); } } /* No data descriptor found on metatype. Look in tp_dict of this * type and its bases */ attribute = _PyType_Lookup(type, name); // BType if (attribute != NULL) { /* Implement descriptor functionality, if any */ descrgetfunc local_get = attribute->ob_type->tp_descr_get; if (local_get != NULL) { /* NULL 2nd argument indicates the descriptor was * found on the target object itself (or a base) */ return local_get(attribute, (PyObject *)NULL, (PyObject *)type); } return attribute; } /* No attribute found in local __dict__ (or bases): use the * descriptor from the metatype, if any */ if (meta_get != NULL) { PyObject *res; res = meta_get(meta_attribute, (PyObject *)type, (PyObject *)metatype); return res; } /* If an ordinary attribute was found on the metatype, return it now */ if (meta_attribute != NULL) { return meta_attribute; } /* Give up */ PyErr_Format(PyExc_AttributeError, "type object ‘%.50s‘ has no attribute ‘%.400s‘", type->tp_name, PyString_AS_STRING(name)); return NULL; } // B.value = 1 static int type_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { PyErr_Format( PyExc_TypeError, "can‘t set attributes of built-in/extension type ‘%s‘", type->tp_name); return -1; } /* XXX Example of how I expect this to be used... if (update_subclasses(type, name, invalidate_cache, NULL) < 0) return -1; */ if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); } |
对比PyObject_GenericGetAttr 和 type_getattro 的 函数实现可以总结出:如果最终获得的属性是一个存在于 class 对象的 tp_dict 中 的 descriptor
时,返回的是 descriptor.__get__ 的调用结果;若 descriptor 存在于 instance 对象的 __dict__ 中,则不会调用其 __get__ 方法,如下图所示。
到这里,我们可以看到 descriptor 对 属性的访问影响主要在两个方面:其一是对访问顺序的影响;其二是对访问结果的影响,第二
种影响正是类的成员函数调用的关键。
回到前面的 py 文件,在 class 对象 A 创建完成后,在 A.__dict__ 中保存了一个与符号‘f‘ 对应的 PyFunctionObject 对象。表达式 a.f()
需要先从 instance 对象 a 找到属性 f,那么 PyObject_GenericGetAttr 返回的是什么对象呢?走一遍流程,回到上面代码注释可以看
到返回的是 descriptor.__class__.__get__(descriptor, a, A) 的执行结果,其实 PyFunctionObject 对象的 class 对象是
PyFunction_Type,与 ‘__get__‘ 对应的tp_descr_get 在 PyType_Ready 中被设置为了 func_descr_get,这意味着 A.f 实际上是一个
non data descriptor,在 fun_descr_get 中将 A.f 对应的 PyFunctionObject 进行了一番包装,生成一个新的 PyMethodObject 对象。
2 3 4 5 6 7 |
typedef struct {
PyObject_HEAD PyObject *im_func; /* The callable object implementing the method */ PyObject *im_self; /* The instance it is bound to, or NULL */ PyObject *im_class; /* The class that asked for the method */ PyObject *im_weakreflist; /* List of weak references */ } PyMethodObject; |
其中 im_func = descriptor; im_self = a; im_class = A; 这样将一个 PyFunctionObject 与 一个 instance 对象通过 PyMethodObject 对象
结合在一起的过程称为成员函数的绑定,如下图所示。
现在我们知道PyObject_GenericGetAttr 返回一个PyMethodObject 对象,a.f() 接下去的操作就是将其压入运行时栈,实际上 a.f 仅仅
是一个带一个位置参数的函数,故需要把 self 参数解析到运行时栈,接下去的函数调用操作就跟 笔记(1)的 p226 条目类似了。
类似地,a.g(10) 可以看作带2个位置参数的函数调用,函数中 self.value = value; 会设置 a.__dict__ 。
p308: Bound Method 和 Unbound Method
当调用Bound Method 时,Python 虚拟机帮我们完成了 PyFunctionObject 对象 与 instance 对象的绑定,instance 对象 自动成为 self
参数;而调用 Unbound Method 时,没有这个绑定导致 im_self 为 NULL, 我们需要自己传入 self 参数。
故我们可以这样调用 Unbound Method:a = A() A.g(a, 10); 最终更改的当然是 instance 对象 a。
p311: PyObject_GenericAlloc 修改为 PyType_GenericAlloc
p310: staticmethod
首先,下图2种方式都可以实现 staticmethod:
实际上 staticmethod 是 <type ‘staticmethod‘>,staticmethod(g) 的过程就是从一个 class 对象 创建 instance 对象的过程。
2 3 4 |
typedef struct {
PyObject_HEAD PyObject *sm_callable; } staticmethod; |
在申请完 staticmethod 结构体决定大小的内存之后,还会调用 __init__ 进行初始化,PyStaticMethod_Type 的 tp_init 设置为 sm_init,在函数内
原来‘g‘ 对应的那个 PyFunctionObject 被赋给了 staticmethod 对象的 sm_callable,并在 A.__dict__ 中关联起来。
因为在 PyStaticMethod_Type 中 tp_descr_get 指向了 sm_descr_get,故实际上 staticmethod 对象也是一个 descriptor,sm_descr_get 直接返回
sm_callable。所有当我们无论通过 a.g 还是 A.g 访问,由于‘g‘ 是位于 class 对象A 的 tp_dict 中的 descriptor,所有会调用其 __get__操作,直接返回
当时最开始与‘g‘ 对应的那个 PyFunctionObject 对象,而不是一般成员函数返回的 PyMethodObject 对象,也就没有了绑定self 参数的过程,所以
‘g‘ 访问不到 a = A(); a.__dict__ 里面的内容,但可以访问到 A.__dict__ 里面的内容,如上图所示。
Python 中的 static method 只是 descriptor 应用的一个例子,还有很多其他特性,如 class method, property 等都是应用 descriptor 的例子。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。