为了更好地利用python语言,无论是python本身还是和C/C++配合使用,深刻理解python的运行原理都是非常重要的,本文以CPython源码为研究对象,深入剖析Python的实现。主要包含:
本文参考: Python源码剖析一书
学习建议:唯有亲身尝试,才能深解其中三昧。 在真实的源码和文章的描述相比较揣摩,动手捣鼓捣鼓Python的源码,用真实的输出来验证文章描述和自己的理解。
首先我们应该了解python的整体架构,对python 有个宏观的认识。
Python整体架构分为三部分,如下图:
获取源码网站: 源码网站
解压后我们将看到目录结构:
我们熟知在面向对象理论中的 “类” 和 “对象” 这两个概念在python中都是使用Python内对象来实现的,也可以理解为一切都是对象, 于是我们将会带着一些疑问进入本章~
额外一些有意思的相关知识:
所有的对象都拥有一些相同的内容, 这些内容就在PyObject中定义, PyObject是整个Python对象体系的核心。
而PyObject的秘密就在这个PyObject_HEAD中~
我们发现,其实在PyObject的定义中, 仅有两个东西:
PyObject中定义了每一个Python对象都必须有的内容, 每一个Python对象除了拥有PyObject外,还会占据一些额外的内存,保存属于自己的特殊信息,比如 intobject就会多出一个 ob_ival 来表示其值。
对于数组,这种 n 个 元素构成的东西也是一类Python对象的共同特征, 因此, 在PyObject对象之外
还有一个表示这类对象的结构体–PyVarObject
变长对象往往都是容器, ob_size 指明了所容纳元素的个数
从PyObject_VAR_HEAD的定义中我们发现, PyVarObject实际上只是对PyObject的扩展而已,其开始部分的字节的意义和 PyObject相同, 一个引用计数加上一个类型信息结构指针,这意味着在Python中对对象的引用变得十分的统一, 使用 PyObject* 指针能引用任意的一个对象。
不同的Python对象在内存布局上的关系
在上文我们看到了Python中所有对象的共有信息的定义,但我们会思考,我们所创建的对象的真实描述信息在哪呢? 例如:Int 和 Str 对象占有的空间信息在哪~, 其实这些信息都被隐含在 PyObject 的 _typeobjct 指针指向的地方。
在_typeobjct 的定义中包含了很多的信息,主要分为4类:
事实上, 一个 PyTypeObject对象就是 Python中对面向对象理论中“类”这个概念的实现,由于PyTypeObject是一个篇幅较大的话题,我们在第二部分来介绍构建在PyTypeObject之上的Python的类型和对象体系。
思考: Python内部究竟如何才能从无到有创建一个整数对象呢?
Python对外提供了C API,分为两种:
PyObject * intObj = PyObject_New(PyObject, &PyInt_Type)
PyObject *intObj = PyInt_FromLong(10)
对于自定义的类型,比如 Class A(object) 定义的类型A,要创建其对象,由于Python不可能事先提供
PyA_New这样的API, 他将会通过A所对应的类型对象 来创建实例对象。
下面我们举例创建整数对象的函数调用流程(如图):
tp_new, tp_init 对应 new操作符和类的构造函数
对象的行为是通过内置大量函数指针来实现的,这些函数指针就表现了类型对象所定义的操作(行为)。
同时,在PyTypeObject有三个重要的操作族需要介绍一下:
tp_as_number, tp_as_sequence, tp_as_mapping,
分别指向 PyNumberMethods, PySequenceMethods 和 PyMappingMethods 函数族。
这里我们以序列操作族为例子:
也可以说当 tp_as_sequence 指针不为空时(list,str),表现出的行为就是序列行为。
在我们的PyTypeObject定义的最开始也会存在 PyObject_Var_HEAD, 证明Python中的类型本身也是一个对象,其类型是 PyType_Type:
这里我们来看一下Int类型的定义,它会调用 PyObject_HEAD_INIT这个宏来初始化公共的头部分:
本质上就是将 ob_type 指向 PyType_Type, 并将其引用计数设置为1
Python利用C语言实现了对象的多态性,Python内部在创建对象时会使用 PyObject * 取保存和维护这个对象,(所有对象的头部是相同的),因此直接可以使用该指针所指对象的 ob_type 域动态去判断,正是这个域的存在,Python实现了多态性。
我们来分析一下:
void Print(PyObject* object) {
object->ob_type.tp_print(object);
}
如果指针本身是一个PyIntObject* ,就会调用到 PyIntObject的类型对象中定义的输出操作,
如果是一个PyStringObject*, 就会调用到 PyStringObject 对象对应的类型对象中定义的输出操作。
Python内建了垃圾回收机制,进行较为繁重的内存管理工作,引用计数正是Python垃圾回收机制的一部分。
注意: 析构函数并不意味着最终会释放内存, 频繁申请和释放会有效率上的问题,因此Python中大量采用了内存池的技术。
我们将 Python 对象从概念上大致分为5类:
在定义上,是对 c 语言 long 的扩展而已~
对于其 ob_type 的指向,则是指向了 PyInt_Type
在PyInt_Type 中保存了关于PyIntObject对象的丰富信息,不仅有其对象应占有的内存大小,文档信息,也包括了支持的操作。
操作定义: 需要注意的是 int_as_number 这个域,他是一个PyNumberMethods对象,PyNumberMethods 有39个函数指针,定义了39种可选的操作(加减乘除),而对于 int_as_number 确定了对于一个整数对象,数值操作是如何进行的。
小知识点: 在整数的相加 int_add 中检查了加法结果是否溢出,用了位运算。
原理:只有两个相同符号的整数相加时才会溢出, 一正一负相加不会溢出, 溢出后得到的结果符号一定与任意一个整数符号相反,即
x = a + b
if ((x^a) >= 0 || (x^b) >= 0) # 和其中一个符号相同就算不溢出
return False
return True
内建类型对象的tp_new, tp_init 操作来创建实例对象, 最终依旧是会调用Python为特定内建对象准备的
CAPI。 为了创建一个 PyIntObject对象,Python提供了3条途径。