足球盘口软件
当前位置: 足球盘口软件 > 前端 >
python对象初探,python源码剖析

 

处于研究python内存释放问题,在阅读部分python源码,顺便记录下所得。
(基于《python源码剖析》(v2.4.1)与 python源码(v2.7.6))

工作整两年了,用python最多,然而对于python内部机制不一定都清楚,每天沉醉于增删改查的简单逻辑编写,实在耗神。很多东西不用就忘记了,比如C语言,正好,python源码用C写的,分析python源码的同时又能温故C语言基础,实在是件很好的事情。另外,还有陈儒大神的《python源码剖析》做指引,分析也不至于没头没脑。期望在一个月的业余时间,能有所小成,以此为记。

 

 

先列下总结:
        python 中一切皆为对象,所以会先讲明白python中的对象,然后开始整理最简单的两个类型,整形和字符串;然后会进一步探索容器类型,会讲 List 和 Dict,以及内存管理机制。有时间精力会总结下《python源码剖析》作者的 python模拟程序、编译的code对象与pyc文件、python虚拟机相关知识,运行环境,模块动态加载、多线程机制。

1 python中的对象

python中,一切东西都是对象,在c语言实现中对应着结构体。首先当然还是从python内建对象开始看起,最基本的是PyIntObject, PyStringObject, PyListObject, PyDictObject这几个,他们分别属于int,string, list, dict类型。从python2.2之后有了new style class之后,这些内置对象都是继承自object类型,object在代码中对应PyBaseObject_Type。比如我们赋值语句a=3,那么a就是一个PyIntObject对象,它的类型是int,在代码中对应PyInt_Type,PyInt_Type也是一种对象,我们称之为类型对象。那么PyInt_Type它的类型是什么呢,答案是type, 对应到代码中就是PyType_Type。当然object也是一个类型对象,它的类型也是PyType_Type。这么一层层下去,PyType_Type也是个对象,那它的类型又是什么呢,没错,答案就是它的类型就是它自己,。看下面的验证代码:

##内建对象测试
In [1]: a = 3

In [2]: type(a)
Out[2]: int

In [3]: type(int)
Out[3]: type

In [4]: type(type)
Out[4]: type

In [5]: int.__base__
Out[5]: object

In [6]: type(object)
Out[6]: type

先分析下几个基础内建对象在C语言中的结构体以及常用的几个宏,为了方便,我用的也是陈儒大神分析的那个版本一致,版本是2.5.6.源码官网有下载。

// 内建对象基础
#define PyObject_HEAD                   
        Py_ssize_t ob_refcnt;           
        struct _typeobject *ob_type;

#define PyObject_HEAD_INIT(type)        
        1, type,

#define PyObject_VAR_HEAD               
        PyObject_HEAD                   
        Py_ssize_t ob_size; /* Number of items in variable part */
#define Py_INVALID_SIZE (Py_ssize_t)-1

typedef struct _object {
        PyObject_HEAD
} PyObject;

typedef struct {
        PyObject_VAR_HEAD
} PyVarObject;

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 */
        destructor tp_dealloc;
        printfunc tp_print;
        getattrfunc tp_getattr;
        setattrfunc tp_setattr;
        cmpfunc tp_compare;
        reprfunc tp_repr;
        ...
} PyTypeObject;

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];
    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
} PyStringObject;

如代码中所示,*PyObject是所有Python对象的基石,所有后续看到的对象都有一个相同的PyObject头部,从而我们可以在源码中看到所有的对象都可以用PyObject指针指向,这就是面向对象中经常用到的多态的技巧了。Python内部各个函数对象间也是通过PyObject传递,即便本身这是一个PyIntObject类型的对象,代码中并不会用PyIntObject指针进行传递,这也是为了实现多态。比如下面的函数:

void Print(PyObject* object) {
    object->ob_type->tp_print(object);
}

另外如代码中注释所说的,变长对象的ob_size指的是元素个数,不是字节数目。

  1. PyIntObject --> long的一个简单包装

    typedef struct{ PyObject_HEAD long ob_ival; } PyIntObject;

1. 在python中,对象就是为C中的结构体在堆上申请的一块内存。对象不能被静态初始化,也不能在栈空间生存。但内建的类型对象都是被静态初始化的。

直入主题,开始总结。

2 python对象引用计数

下面是几个常用的操作对象引用计数的宏定义(object.h),一并列出,这里去除了一些调试时用的代码,更容易看明白代码含义。Py_NewReference是初始化时对象时设置引用计数, Py_INCREF和Py_DECREF分别用来增加引用技术和减少引用计数。从代码中可以看到,python增加引用和减少引用都是通过这些宏操作的,**有一点需要注意的是,当对象引用ob_refcnt减小到0时,会调用对象的析构函数,析构函数并不一定会调用free释放内存空间,因为频繁申请和释放内存严重影响性能,所以在后面看到python有大量用到内存池技术,对提升性能有很大效果。

需要说明的是,类型对象是不在引用计数规则之中的,每个对象指向类型对象的指针并不视为类型对象的引用,也就是说不会影响类型对象的引用计数,类型对象永远不会被析构。

#define _Py_NewReference(op) ((op)->ob_refcnt = 1)


#define _Py_Dealloc(op) (*(op)->ob_type->tp_dealloc)((PyObject *)(op)))

#define Py_INCREF(op) ((op)->ob_refcnt++)

#define Py_DECREF(op)                                   
        if (--(op)->ob_refcnt != 0)                     
            ;
        else                                            
            _Py_Dealloc((PyObject *)(op))

#define Py_CLEAR(op)                            
        do {                                    
                if (op) {                       
                        PyObject *tmp = (PyObject *)(op);       
                        (op) = NULL;            
                        Py_DECREF(tmp);         
                }                               
        } while (0)

/* Macros to use in case the object pointer may be NULL: */
#define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)
#define Py_XDECREF(op) if ((op) == NULL) ; else Py_DECREF(op)

PyInt_Type --> PyIntObject的类型对象。与对象相关的元信息实际上都是保存在与对象对应的类型对象中的

  1. 对象创建后大小不变。可变对象有一指针指向可变大小的内存区域。

  2. 对象机制的基石:PyObject


3 Python对象分类

python中的对象大致可以分为下面几类:

  • 数值对象:如integer,float,boolean
  • 序列集合对象:如string,list,tuple
  • 字典对象:如dict
  • 类型对象:如type
  • 内部对象:如后面会看到的code,function,frame,module以及method对象等。
PyTypeObject PyInt_Type = {
  PyObject_HEAD_INIT(&PyType_Type)
  0,
  “int”,
  //…
}

定长对象:

一、Python对象

4 参考资料

  • 《python源码剖析》

PyIntObject 所支持的操作

typedef struct _object{
           PyObject_HEAD
}PyObject;

1、对象

int_dealloc  //删除 PyIntObject 对象 
int_free  //删除 PyIntObject 对象 
int_repr  //转化成 PyString 对象 
int_hash  //获得 HASH 值 
int_print  //打印 PyIntObject 对象 
int_compare  //比较操作 
int_as_number  //数值操作 
int_methods  //成员函数

release模式编译-->

2、对象类型

PyIntObject是定长对象,不可变对象 --> 因为不可变,所以对象池里的每一个PyIntObject对象都能够被任意地共享,不用担心被修改。

 

3、继承与多态

  1. PyIntObject对象的创建和维护
    Python C API创建PyIntObject的三种途径

    PyObject PyInt_FromLong(long ival) PyObject PyInt_FromString(char s, char **pend, int base) #ifdef Py_USING_UNICODE PyObject PyInt_FromUnicode(Py_Unicode *s, int length, int base) #endif

typedef struct _object{
           intob_refcnt; // 引用计数,垃圾收集机制
           struct_typeobject *ob_type;// 类型信息,指定对象类型
}PyObject;

4、引用计数

PyInt_FromString和PyInt_FromUnicode利用了设计模式中的Adaptor Pattern思想对整数对象的核心创建函数PyInt_FromLong进行了接口转换

PyObject是python对象共有,对象在内存中最开始的字节。

5、对象分类


  1. 整数对象池
    小整数对象--> 缓存池缓存
    大整数对象--> 专用内存池轮流使用
    小整数对象与大整数对象的分界点

    #ifndf NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 static PyIntObject *small_ints[NSMALLPOSINTS + NSMALLPOSINTS]; #endif

变长对象:

1、对象

python中一切皆为对象,包括自定义类型,int、str、list、dict等都是对象,先看看所有对象的基石:

*定长对象(int,str):

[object.h]

typedef struct_object{

PyObject_HEAD

}PyObject;

*变长对象(list,dict..):

[object.h]

typedef struct{

PyObject_VAR_HEAD

}PyVarObject;

//前者依赖于PyObject_HEAD,后者依赖于PyObject_VAR_HEAD,看看两者不同:

图片 1

图1-1-1

由图1-1-1可见,定长对象中有

ob_refcntob_type这两个变量,变长对象中多了一个ob_size变量;其中,ob_refcnt用于引用计数机制,ob_type是一个指向_typeobject结构体的指针,ob_size指变长对象中包含的元素个数。

ob_refcnt后面内存回收时再讲,大概就是某个对象A,对其有引用时引用计数增加,释放时引用计数减少,引用计数为0时将回收对象A,从堆上删除释放内存。

ob_type_typeobject结构体,用于指定一个对象类型的类型对象。有点拗口,下节讲。

ob_size用于指明容器对象中拥有元素的个数,不是字节数。


3.1 通用整数对象池(大整数对象池)
PyIntBlock的结构

 

2、对象类型

现在分析上节提到的_typeobject,类型对象


图片 2



图1-2-1

_typeobject是比较大的一个结构体,主要有四类信息:

1、类型名tp_name,用于pyhton内部及调试;

2、创建该类型对象时分配内存空间大小信息,tp_basicsizetp_itemsize

3、与该对象有关的操作信息,如hashfunc(函数指针),操作主要分为标准操作、标准操作族、其他操作;

4、类型信息;

_typeobject头部中有PyObject_VAR_HEAD,说明类型也是一个对象,而类型对象的类型则是PyType_Type(图1-2-2):

图片 3

图1-2-2

例如整形int(图1-2-3):

图片 4

图1-2-3

其运行时对象类型关系(图1-2-4):

图片 5

图1-2-4


#define BLOCK_SIZE 1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8    /* Enough for a 64-bit pointer */
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / 
sizeof(PyIntObject))
struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;//维护PyIntBlock的单向列表
static PyIntObject *free_list = NULL;//管理全部block的objects中的所有空闲内存
#define PyObject_VAR_HEAD 
           PyObject_HEAD
           intoob_size; // 可变长部分可容纳的元素个数

typedef struct{
           PyObject_VAR_HEAD
}PyVarObject;

3、继承与多态

通过前面的PyObject和类型对象,Python利用C语言实现继承和多态。首先,Python中所有内建对象和内部使用对象在最开始内存区域都有一个PyObject,相当于这些对象都继承于PyObject;在建立一个对象时,如PyIntObject,这对象由PyObject*维护而非PyIntObject*维护,而这指针指向的类型只能从ob_type域判断,从而实现多态。


PyInt_FromLong函数

在Python内部,每一个对象都拥有相同的对象头部。我们只需要用一个PyObject *指针就可以引用任意的一个对象。

4、引用计数

Python内建了垃圾收集机制,使用每个对象共有的ob_refcnt来维护引用计数,通过PyINCREF(op)和Py_DECREF(op)两个宏来增减对象引用计数,当引用计数为0后通过tp_dealloc释放其内存和系统资源。在对象初始化时,通过_Py_NewReference(op)将对象引用计数初始化为1。(代码如图1-4-1)

图片 6

图1-4-1

还有要注意的是,但引用计数减为0时,会调用该对象的析构函数,但不一定会调用free,频繁申请、释放内存降会低执行效率,Python使用内存池计数作为补充。


  1. 如果小整数对象池机制被激活,则尝试使用小整数对象池
  2. 如果还能使用小整数对象池由使用通用的整数对象池。
    fill_free_list
  3. 申请一个新的PyIntBlock结构
    2. 将objects中的所有PyIntObject对象通过指针依次连接起来,形成一个free_list为表头的链表
    tp_dealloc
    一个PyIntObject对象被销毁时,它所占有的内存并不会被系统释放,而是变成了自由内存被链入了free_list所维护的自由内存链表

eg:

**5、对象分类 **

1、Math:数值对象

2、Container:容器对象

3、Composition:程序结构对象

4、Internal:内部使用对象

图示(图1-5-1):

图片 7

图1-5-1


问题:ob_type不是用来指向类型对象的吗,在fill_free_list中它却被用来形成链表,那谁来指向类型对象,没有类型对象又怎么调用类型对应的操作?为什么要形成链表?
解析:在fill_free_list中,申请的PyIntBlock空间中的objects(即PyIntObject数组)还没有初始化,即没有被使用,ob_type放着没用,所以可以用来形成链表。当创建新的PyIntBlock时,ob_type会指向PyInt_Type。这里不知道我的理解对不对,如果我的理解对的话,书上的图2-7就有问题,已经创建了的PyIntBlock不应该有一个指向自由内存空间的指针,只有当它被销毁才会重新指向自由内存空间
形成的链表主要是用来维护还没有objects中空闲的内存块,链表的表头由free_list所指向。
3.2 小整数对象池
在pythonrun.c(python环境初始化时)中调用_PyInt_Init函数完成小整数对象池的初始化。

 

图片 8

//PyIntObject对象
typedef struct{
           PyObject_HEAD
           longob_ival; // 保存值
}
//pythonStringObject对象
typedef struct{
           PyObject_VAR_HEAD
           longob_shash;
           into b_sstate;
           charob_sval[1];
} PyStringObject;
//PyListObject对象
typeDef struct{
           PyObject_VAR_HEAD
           PyObjct**ob_item;
           intallocated;
}PyListObject;
//PyDictObject对象
typedef struct{
           Py_ssize_tme_hash;
           PyObject*me_key;
           PyObject*me_value;
}PyDictEntry;

 

图片 9

  1. Hack PyIntObject
    自己实验的结果:
    图片 10
  1. 类型对象(_typeobject类型--> 面向对象理论中“类”概念的实现 )

 

 

图片 11

typedef struct _typeobject{
           PyObject_VAR_HEAD//类型对象也是对象,而且是变长对象
           char*tp_name;  //类型的名称,打印时会用到
           inttp_basicsize, tp_itemsize;  //该类型占用内存的大小,分配内存时会用到

           /*与该类型对象相关联的操作信息*/
destructor tp_dealloc;
printfunc tp_print;
//…
} PyTypeObject;

 

PyType_Type (metaclass), PyInt_Type, PyString_Type,PyDict_Type, PyList_Type是 _typeobject的实例化对象

. PyIntObject -- long的一个简单包装 typedef struct{PyObject_HEADlong ob_ival;} PyIntObject; PyInt_Type -- PyIntObject的类型对象。与对象相关的元信息实际上都...

  1. 引用计数

 

Py_INCREF(op) //增加一个对象的引用计数
Py_DECREF(op) //减少一个对象的引用计数

当引用计数减少到0之后,Py_DECREF将调用该对象的析构函数来释放该对象占有的内存和系统资源。这个析构动作是通过对象对应的类型对象中定义的一个函数指针来指定的,即tp_dealloc

调用析构函数并不意味着最终一定用调用free释放空间,Python中大量采用了内存对象池技术来避免频繁地申请和释放内存空间。

类型对象永远不会被析构。

  1. 对象的创建

6.1Python C API(内建类型一般采用这种方式)

 

//范型的API
PyObject *intObj = PyObject_New(PyObject, &PyInt_Type);
//类型相关的API
PyObject *intObj= PyInt_FromLong(10)

 

6.2类型对象PyInt_Type (自定义类型只能通过这种方式)

int(10) 就是通过PyInt_Type创建了一个整数对象

所有类都是以object为基类的

tp_new --> 对应C++中的new 操作符,申请内存

tp_init --> 对应C++中的构造函数,完成“初始化”对象的操作

  1. 对象的行为

PyTypeObject中定义了大量的函数指针,比如 tp_new, tp_init, tp_dealloc, tp_hash,其中有三组非常重要的操作族

tp_as_number -->PyNumberMethods

tp_as_sequence --> PySequenceMethods

tp_as_mapping --> PyMappingMethods

 

typedef PyObject *(*binaryfunc) (PyObject *, PyObject *);
typedef struct{
 binaryfunc nb_add;
 binaryfunc nb_subtract;
 //…
} PyNumberMethods;

typedef struct {
 lenfunc sq_length;
 binaryfunc sq_concat;
 ssizeargfunc sq_repeat;
 ssizeargfunc sq_item;
 ssizessizeargfunc sq_slice;
 ssizeobjargproc sq_ass_item;
 ssizessizeobjargproc sq_ass_slice;
 objobjproc sq_contains;
 /* Added in release 2.0 */
 binaryfunc sq_inplace_concat;
 ssizeargfunc sq_inplace_repeat;
} PySequenceMethods;

typedef struct {
 lenfunc mp_length;
 binaryfunc mp_subscript;
 objobjargproc mp_ass_subscript;
} PyMappingMethods;

 

 

  1. 类型的类型(PyType_Type)

用户自定义class对应的PyTypeObject是通过PyType_Type创建的。所有class的class都是PyType_Type

 

PyTypeObject PyType_Type = {
           PyObject_HEAD_INIT(&PyType_Type)
           0,
           “type”,
           //…
};

图片 12

一般类型对象与PyType_Type对象之间的关系

图片 13

 

  1. Python对象的多态性

通过PyObject和PyTypect,Python利用C语言完成了C++所提供的对象的多态的特性。Python内部会用一个PyObject *变量,而不是通过一个PyIntObject *(或者其它具体的类型)来保存来维护这个对象,我们并不知道这个指针所指的对象是什么类型,但可以通过所指对象的ob_type域动态进行判断。

eg.

 

void Print(PyObject *object){
           object->ob_type->tp_print(object);//根据object的具体类型调用对应的tp_print。一个函数在不同情况下表现出了不同的行为,即多态
}

 

. 在python中,对象就是为C中的结构体在堆上申请的一块内存。对象不能被静态初始化,也不能在栈空间生存。但内建的类型对象都是被静态...

返回顶部