赞
踩
友情提示:类的创建过程非常复杂, 请自备小本本
先来个最简单的类:
class A:
pass
编译一下:
0 LOAD_BUILD_CLASS
2 LOAD_CONST 0 (<code object A at 0x00000226D1158ED0, file "", line 1>)
4 LOAD_CONST 1 ('A')
6 MAKE_FUNCTION 0
8 LOAD_CONST 1 ('A')
10 CALL_FUNCTION 2
12 STORE_NAME 0 (A)
可以看到第一条字节码就不认识了,好吧,暂时不管他是干啥的,往下看,接下来三条字节码创建了一个函数对象,codeobject正是类的定义体,名字叫A,欸?定义类怎么变成定义函数了?
好吧,带着问题继续看,CALL_FUNCTION,终于调用了!等等,CALL_FUNCTION 的参数是2,说明有2个参数,往上数两个,正好是’A’和刚才创建的函数对象,参数有了,那被调用的函数呢,没了?这是不可能的,唯一的理由就是LOAD_BUILD_CLASS在堆栈中push了一个函数,看看LOAD_BUILD_CLASS的源码:
bc = _PyDict_GetItemId(f->f_builtins, &PyId___build_class__);
原来是builtins里面的一个叫_build_class_的东西,这个东西可能就是开始创建类的第一现场!
是时候来分析下过程了,全局搜索一下build_class,找到了这个一个函数:builtin___build_class__,看样子应该没错了,看下长短,150行。还行:
首先肯定是检查参数:
if (!PyTuple_Check(args)) {
PyErr_SetString(PyExc_TypeError,
"__build_class__: args is not a tuple");
return NULL;
}
nargs = PyTuple_GET_SIZE(args);
if (nargs < 2) {
PyErr_SetString(PyExc_TypeError,
"__build_class__: not enough arguments");
return NULL;
}
func = PyTuple_GET_ITEM(args, 0); /* Better be callable */
if (!PyFunction_Check(func)) {
PyErr_SetString(PyExc_TypeError,
"__build_class__: func must be a function");
return NULL;
}
name = PyTuple_GET_ITEM(args, 1);
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError,
"__build_class__: name is not a string");
return NULL;
}
可以看出来一点有用的信息:
1. args是tuple,这是cfunction函数调用约定,这里不多说了
2. args至少长度为2,是的,至少得有上面字节码里的类名和functionobject
3. 然后取出类名和functionobject放在func和name变量里备用
然后获取基类列表:
bases = PyTuple_GetSlice(args, 2, nargs);
这个很容易理解,前两个参数刚才已经用了,后面的位置参数肯定就是基类了。
确定元类是一个略微复杂的过程:
如果关键字参数为空,也就是metaclass没有指定:
1. 没有基类
钦定为type
meta = (PyObject *) (&PyType_Type);
meta = (PyObject *) (base0->ob_type);
这还没完呢,取得了暂时的metaclass还要去和基类列表比较,看有没有冲突之类的,这里就略过了,不是很重要。。
然后调用了meta的_prepare_:
prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);
这个ns默认情况下就是一个空的dict,然而却很重要,因为他就是我们创建的类的_dict_,所以我们可以通过复写元类的_prepare_方法改变这个规则,比如改成一个orderdict?
还有最后一个准备工作,那就是求值类体,用上面的ns作为locals,这样,所有在类中定义的名字都放入了ns
准备工作做完后,要进入正式的类的创建过程了,首先构造参数:
PyObject *margs[3] = {name, bases, ns};
分别是类名,基类列表,dict(看到了吧)。
然后调用元类:
cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
很明显会进入meta的_call_,然而在call中,直接调用了_new_:
obj = type->tp_new(type, args, kwds);
所以还是直接看_new_把:
500多行代码,列出来没人看。。。。捡重要的说。
国际惯例,参数验证:
如果meta就是PyType_Type参数只有一个,那么就是type(x)这种东西了,很简单,直接返回x的类型:
return (PyObject *) Py_TYPE(x);
然后是取参数,验证metadata,我们就不看了。直接看创建过程把
首先是基类,如果基类tuple为空,则给个PyBaseObject_Type,即object,所有类的基类。
然后处理dict,就是上面的ns,保存了所有类里面定义的名字
1. _slots_
第一步是看看类里面有没有定义slots,如果不知道slots是什么,请先看python教程。。。。。如果有,则进行一些名字改造,拼接,一般是拼上一个 _classname
__private => _classname__private
然后赋值给ht_slots域。这里有一点,动态创建的类和内建类型有点不一样,动态创建的类是PyHeapTypeObject,而内建的类是PyTypeObject。区别就在PyHeapTypeObject后面跟了额外几个域:
typedef struct _heaptypeobject {
/* Note: there's a dependency on the order of these members
in slotptr() in typeobject.c . */
PyTypeObject ht_type;
PyAsyncMethods as_async;
PyNumberMethods as_number;
PyMappingMethods as_mapping;
PySequenceMethods as_sequence; /* as_sequence comes after as_mapping,
so that the mapping wins when both
the mapping and the sequence define
a given operator (e.g. __getitem__).
see add_operators() in typeobject.c . */
PyBufferProcs as_buffer;
PyObject *ht_name, *ht_slots, *ht_qualname;
struct _dictkeysobject *ht_cached_keys;
/* here are optional user slots, followed by the members. */
} PyHeapTypeObject;
就是普通的pytypeobject后面跟了一堆东西。
然后赶紧利用起来:
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;
原来是把内建类型的几个方法集合放在了结构体后面而已。。。。
然后就是处理一些简单的属性比如tp_name什么的,都是简单的赋值
最后一个关键的地方就是slot的处理,这个slot和_slots_不一样,这个slot是python内部的东西,它处理了一个方法调用的走向。比如继承自内建type的类,复写其特殊方法,比如 继承int,复写 _add_,那么当做加法时,_add_会被调用,这是怎么实现的呢?
都在fixup_slot_dispatchers这个函数里面了。
这个函数遍历了slotdefs这个静态数组,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。