接触python有段时间了,说实话,当我第一次用的时候就喜欢上了这门语言,那种编程的流畅感真的让人耳目一新。但这将近一年来,我只是用python小打小闹的写个小游戏,分析个数据,还参加了一次数学建模比赛,并没有系统的了解它。最近突然想要深入学习这门语言,所以找了一些资料,意外发现了一本陈儒先生写的《python源码分析》,就干脆跟着这本书从源码开始,认真了解这门语言。同时,通过博客和大家分享一下我的学习经历
一 认识源码
俗话说,“巧妇难为无米之炊”,要分析源码首先要得到源码。当然,python的源码很容易获得,这里给出官网的下载地址:
https://www.python.org/ftp/python/2.7.6/Python-2.7.6.tgz
虽然书中分析的是几年前的2.5版本的python,但是本着与时俱进的原则,我们还是用最新版本的2.7.6的源码。
解压缩后的目录结构如下:
介绍几个主要模块:
Include:python用到的所有的头文件,如果你想过用C/C++模块拓展python,就可以用到这里的头文件
Lib:这里包含了所有用python语言编写的内部标准库
Modules:包含了所有用C语言编写的模块,主要是一些对速度要求比较高的模块,如readline、tkinter等
Parser:包含了python的解释器中scanner和parser等部分,用来对python代码进行句法分析,此外还有一些很给力的小工具,能自动生成python词语法分析器
Objects:包含了所有python的内建对象,包括整数、list、dict等
Python:包含了python解释器的Compiler和执行引擎部分,从名字也看得出,是python的核心所在
Tools:一看就知道,这是各种工具的集合
Pcbuild:Visual Studio的工程文件,分析源码时很有用
二 python内建对象
了解python的人都知道,python中所有的东西都被当作对象处理,变量、函数甚至数据类型都无一例外,python中大量的内建对象也无疑是python核心的重要组成部分,也正是python设计哲学的体现,因此,我们就从内建对象开始,一步步征服python源代码。
首先,我们打开刚才的Include文件夹,找到object.h 头文件,看看python中的对象是如何定义的。
Python中,所有的东西都是对象,而所有的对象都拥有相同的内容,这些都定义在PyObject结构体中,可以说是python对象机制的核心:
[object.h]Typedef struct_object{
PyObject_HEAD
}PyObject;
事实上,python对象所有的秘密都藏在这个叫PyObject__HEAD的宏中:
[object.h]#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
#define _PyObject_EXTRA_INIT 0, 0,
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
这里涉及到了条件编译的概念,翻译一下:
1 如果定义了 Py_TRACE_REFS:
/* 定义一个指针指向一个保存所有堆中存活的对象的双向链表 */
_PyObject_HEAD_EXTRA 替换为两行代码 (其中 \ 符用来续行):
struct _object *_ob_next; struct _object *_ob_prev;
定义 _PyObject_EXTRA_INIT 为 0, 0,
2 如果没定义Py_TRACE_REFS:
_PyObject_HEAD_EXTRA 与 _PyObject_EXTRA_INIT都定义为空。(防止报错)
/* PyObject_HEAD 定义了每个Pyobject的初始化部分 */
PyObject_HEAD 替换为三行代码:
_PyObject_HEAD_EXTRA;Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
当我们在vs的release模式下编译时,不会定义Py_TRACE_REFS。 因此,最终的PyObject定义很简单:
[object.h]Typedef struct_object{
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
}PyObject;
在这里,我们不妨用vs的追踪一下这里的Py_ssize_t,看看他到底是什么。用vs打开PcBuilt文件夹中的解决方案,打开pythoncore工程/外部依赖项/object.h,找到但蓝色字体标记的Py_ssize_t,按F12转到定义:
[pyport.h]typedef ssize_t Py_ssize_t;
继续追踪:
[pyconfig.h]typedef _W64 int ssize_t;
这里的_W64是为了兼容64位系统,可以忽略,所以Py_ssize_t本质就是int。因此,最终的PyObject的定义为:
[object.h]Typedef struct_object{
int ob_refcnt;
struct _typeobject *ob_type;
}PyObject;
2 变量的作用
下面,我们分析一下PyObject中的两个参数:
1 ob_refcnt: 实质是int整数,用来实现垃圾管理机制,代表对象的引用次数,当它为零时,说明没有任何 变量名引用这个对象了,因而对象会被处理掉,腾出内存
2 ob_type:类型对象,用来指向表示对象类型。怎么,不懂?别急,留个伏笔,后面我会详细讲。
所以python 中对象机制的核心很简单:一个引用计数,一个类型信息。
3 其他的部分
作为python对象机制的基础,pyobject定义了所有对象都必须有的部分,它占据了每个对象内存空间的最前端。最前端,意思就是除了Pyobject外,每个对象还应该占有更多的内存空间,还应该有自己“其他的部分”了?这是肯定的,因为要是所有的对象都只包含这两个东西,那么岂不是只有一种对象了,这太不现实了。
那么,这“其他的部分”又有什么呢?
[intobject.h]typedef struct{
Pyobject_HEAD
long ob_ival;
}PyVarObject;
这是python中的整数对象的定义,它里面除了Pyobject_HEAD外,还有一个long型的变量ob_ival,不用说你大概也猜到了,这是整数的数值。同理,字符串对象,list和dict对象,还有其他成千上万的对象,都是这种模式,即Pyobject_HEAD + 自己的“其他的部分”。
4 变长与定长对象
我们知道,int在python只是一个整型变量,他的“其他东西”就是一个int型数值ob_ival,他占的内存大小是固定的,因此被称为“定长对象”。当然,与之对应的,python中同样会有“变长对象”,即他们的内存长度会随着情况不同而改变,举个很简单的例子:字符串。与int类似,python中的字符串的“其他的部分”显然应该是“一个字符串”。当然,我们都知道,C语言中没有“一个字符串”的概念的,只有“N个char型变量的数组”。但是,即使是这样,这N也是个不确定因素,“python”与“java”占内存大小不会是一样的,与之类似的明显还有包含“N个Object对象”的list、“N个Key—Value”的dict,这些,就是“变长对象”。
那这个变长对象python又是怎么描述的呢?接着往下看:
[object.h]#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size; /* Number of items in variable part */
…
typedef struct {
PyObject_VAR_HEAD
} PyVarObject;
原来,在PyObject对象之外,还有一个表示对象的结构体——PyVarObject。
我们看到,PyVarObject中多了一个ob_size变量,它的作用就是用来表示上面的N,用来改变对象的长度。有一点要注意的,那就是ob_size 代表的是对象中的元素个数,而不是对象占的字节数,比如“abc”的N为4(别忘了结束符\0),[1,2,3,4,5]的N为5。
而从PyVarObject的定义看出,他只是一个PyObject的拓展,其开始部分与PyObject相同,都是Pyobject_HEAD,因此,python中的对象引用非常统一,使用一个PyObject*类型的指针就可以调用任意一个对象。
据此,我们有理由推测,python中变量不用声明类型,拿来即用的强大功能,正是源自于此。
5 类型对象
目前,我们知道,通过PyVarObject的ob_size变量,可以改变变长对象的内存长度。然而,我们还并没有看到python具体是怎么做到这点的。还记不记得,在前面讲PyObject_HEAD的定义时,我埋了一个伏笔,那就是_typeobject *ob_type,那个表示对象类型的指针。
现在,我们转到 _typeobject的定义:
typedef struct _typeobject { PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
...
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
...
} PyTypeObject;
承认信息量有点大,简单梳理一下,主要有四条信息:
1 类型名,tp_name,格式为<module>.<name>,用于内部调用和方便调试
2 创建对象时分配内存空间大小的变量, tp_basicsize和tp_itemsize,分别代表基本的大小和里面元素的 大小
3 与对象相关的操作,如tp_print这样的函数
4 要描述的本类型的其他信息
不知道你们有没有发现,这里的PyTypeObject结构体,实际上实现了面向对象编程里“类”的概念,不仅是因为它包含变量属性和方法,背后还有很多学问,再此同样埋个伏笔,后面会结合python的类型与对象体系详细分析。
好了,今天先写这些,下篇继续。