python3.6 源码分析(五):类的创建
来源:互联网 发布:看门狗pc优化补丁 编辑:程序博客网 时间:2024/06/05 06:54
友情提示:类的创建过程非常复杂, 请自备小本本
字节码分析
先来个最简单的类:
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
是时候来分析下过程了,全局搜索一下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);
- 有基类
取第一个基类的metaclass
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这个静态数组,
- python3.6 源码分析(五):类的创建
- python3.6 源码分析(三):创建函数
- Docker源码分析(五):Docker Server的创建
- Docker源码分析(五):Docker Server的创建
- Docker源码分析(五):Docker Server的创建
- python3.6 源码分析(一)
- netty(五) NIO创建的TimerServer源码分析之客户端
- Spark的standalone源码分析(五)
- python3.6 源码分析(二):另一个例子
- python3.6 源码分析(四):函数调用
- quake3源码分析(五)
- Logcat源码分析(五)
- pomelo源码分析(五)
- mosquitto源码分析(五)
- openMPM源码分析(五)
- mosquitto源码分析(五)
- mosquitto源码分析(五)
- DispatcherServlet 源码分析(五)
- oracle存储过程基础语法+提升+例子总结
- 对Python生成器的理解
- android 网络协议等socket,http,HTTPS,get。post请求 等详解
- Error:Execution failed for task ':app:transformClassesWithJarMergingForDebug'. > com.android.build.a
- 【Mysql】主从配置,实现读写分离
- python3.6 源码分析(五):类的创建
- 很简单的git和GitHub入门
- 【BZOJ2208】【JSOI2010】连通数 传递闭包
- 2017.11.4
- Android开发禁止返回键与禁止返回上一次
- 并查集
- 算术运算的应用
- Oracle存储过程实例集锦
- HDU4349-Xiao Ming's Hope(Lucas定理)