Python内部机制-PyObject对象

Posted zhangyifei216

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python内部机制-PyObject对象相关的知识,希望对你有一定的参考价值。

PyObject对象机制的基石

学过Python的人应该很清楚,Python中一切都是对象,所有的对象都有一个共同的基类,对于本篇博文来说,一切皆是对象则是探索Python的对象机制的一个入口点.我假设读者在阅读本文的时候已经下载Python(Python-2.7.11)的源码,并且已经解压进入了源码的根目录下.众所周知Python是用C实现的,C是一种OO的语言,而Python是一个OOP的语言,那么如何在C语言层面实现OOP,实现多态,这是一个有意思的话题,这也是本文需要进行探索的点.Python内部使用了一个PyObject结构体来保存所有对象共同的数据成员,以及实现GC机制所需要的一些辅助字段等.所以可以说PyObject就是Python对象机制的基石,这毫不为过.那么让我们进入到源码中.透过源码看看Python中的对象到底是个啥?

PyObject对象 

typedef struct _object 
    PyObject_HEAD          
 PyObject;      

./Include/object.h

异常的简单,这一切都要归功于PyObject_HEAD宏,C语言中的宏是把双刃剑.看看PyObject_HEAD长什么样吧

#define PyObject_HEAD                   \\
    _PyObject_HEAD_EXTRA                \\
    Py_ssize_t ob_refcnt;               \\
    struct _typeobject *ob_type;        

./Include/object.h

初看起来,还是不容易理解的,不过经验告诉我_PyObject_HEAD_EXTRA这个宏可以不用管,因为这是以下划线开头的,这类变量一般都是内部使用.为此我找到这个宏验证了我的想法.

/* Py_DEBUG implies Py_TRACE_REFS. */   
#if defined(Py_DEBUG) && !defined(Py_TRACE_REFS)
#define Py_TRACE_REFS       
#endif                      

/* Py_TRACE_REFS implies Py_REF_DEBUG. */         
#if defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG)
#define Py_REF_DEBUG        
#endif                      

//注意看这里,只有定义了Py_TRACE_REFS这个宏_PyObject_HEAD_EXTRA才不是空.
//而Py_TRACE_REFS这个宏只有在Py_DEBUG有定义的情况下才会定义,可想而知 _PyObject_HEAD_EXTRA只有在DEBUG模式
//才有意义,否则就是一个空.            
#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 

./Include/object.h

_PyObject_HEAD_EXTRA我们可以略过不看了,这个宏的作用仅仅是在DEBUG模式下,将所有的对象使用双链表链起来方便DEBUG而已.去掉_PyObject_HEAD_EXTRA后再来看看PyObject长啥样吧.

typedef struct _object 
    Py_ssize_t ob_refcnt;               \\
    struct _typeobject *ob_type;        
 PyObject;   

./Include/object.h

异常的清晰是不是,Py_ssize_t只不过是对C语言的基本类型的一个typedef而已,这里可以认为就是ssize_t类型了,ob_refcnt指的就是上文中说的GC机制需要的辅助字段用于维护当前对象的引用个数,ob_type是个啥呢?,这个东西说简单点就是指明当前对象是何种类型.后面会单独拿出来详细的介绍,接下来看下如何使用PyObject来搭建整个Python的对象机制,下面是int对象的底层表示:

typedef struct 
    PyObject_HEAD
    long ob_ival;
 PyIntObject

./Include/intobject.h

包含了PyObject,除此之外包含了一些这些对象特有的一些数据成员,比如这里的ob_ival,就是用来保存int对象的具体数值的.那么String对象可想而知应该有一个char*的指针指向一段heap的内存空间,还需要有一个数据成员来保存当前的String的字符个数,那么list对象呢?,dict对象呢?,细想一下这些对象都应该包含一个指明当前对象包含的元素的个数的数据成员,好吧,这里我们找到了一些共同点,到这里产生了一个技术问题的抉择了,int对象不需要有指明当前对象包含元素个数的数据成员,而string对象,list对象,dict对象都需要有,那么这个数据成员应该放在PyObject中,还是归为每个对象自己特有的数据成员呢?,Python选择了前者将这个数据成员放到了PyObject中,但又没完全这样做.这一切都归功于Python中的另外一个概念,可变对象和不可变对象.下面通过源码我们来看看Python到底是怎么做的

PyVarObject对象

typedef struct 
    PyObject_VAR_HEAD
 PyVarObject;

./Include/object.h

弄了一个PyVarObject对象出来了,其实这是一个所谓的纸老虎,打开PyObject_VAR_HEAD这个宏你就会发现真相原来是这样.

#define PyObject_VAR_HEAD               \\
    PyObject_HEAD                       \\
    Py_ssize_t ob_size; /* Number of items in variable part */

./Include/object.h

又是异常的简单,就是在原有的PyObject基础上加了一个ob_size成员,用来指明当前对象所拥有的元素个数.但是有一个需要注意就是ob_size数据成员的位置只能在PyObject_HEAD下面,目的是为了可以通过PyObject强制转换成任何一个对象.如果你不明白,那么来张图看看.

回到Python

源码看完了,是不是有点想大展宏图的冲动,可惜按照我们目前的水平,相对Python做些什么恐怕还不够格,不过我们可以利用Python来验证下我们读源码的收获.通过上文分析的PyObject,还有PyIntObject,我们来计算一下一个PyIntObject大小,

typedef struct 
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;        
    long ob_ival;
 PyIntObject

一个ob_refcnt这是一个ssize_t类型,64为系统下是8个字节,一个ob_type是个指针,在64位系统下同样也是8个字节,一个long类型在64位系统下同样也是8个字节所以算起来一个PyIntObject是24个字节,回到Python中我们来验证一下:

>>> import sys
>>> inttype = 1
>>> sys.getsizeof(inttype)
24

以上是关于Python内部机制-PyObject对象的主要内容,如果未能解决你的问题,请参考以下文章

Python源码分析:PyObject对象的起源与多态的实现

Python的垃圾回收机制

Python虚拟机之函数机制

**Python垃圾回收机制

Python从门到精通:包装-03-对象处理

PyObject, PyTypeObject - Python 中的 '对象' 们