`

Python源码剖析(1)

阅读更多

 

接触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的类型与对象体系详细分析。

好了,今天先写这些,下篇继续。

 

 

 

  • 大小: 8.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics