[python源码剖析]python对象初识
在python世界里,一切皆为对象。一个整数是一个对象,一个字符串也是对象,一个类也是一个对象。更为神奇的是,一个类型也是一种对象。换句话说,面向对象理论中的“对象”、“类”,到了python中,都成为了对象。
int类型、list类型、dict类型等等,这些都是python内建的类型对象。这些类型对象实现了面向对象中“类”的概念:这些类型对象通过实例化,可以创建内建对象类型对象的实例对象。而这些实例对象,对应到面向对象中的“对象 ”。
同时,python允许我们定义自己的类型对象。基于这些类型对象,我们可以通过实例化来创建属于自己的实例对象。
python对象系统是一个庞大而复杂的体系。接下来的剖析过程中,我会将重点放在对象在python内部是如何表示的。更准确的说,python是用C实现的,所以首先要弄清楚的一个问题就是:对象,这个神奇的东西,在C的层面上,究竟长的什么样子?除此,我们还会了解到类型对象在C层面是如何实现的,并认识类型对象的作用及它与实例对象的关系。
1.python内的对象
正如我们所知,C是面向过程的语言,而python中,一切皆为对象,那么用C实现的python又是如何来实现对象机制的呢?
对于计算机来说,对象是一个抽象的概念。它并能理解这是一个整数,那是一个字符串,它所知道的是,一切都是字节。那么对于计算机来说,什么是对象呢?答案是:一片被分配的内存空间。这片内存空间可能连续,也有可能是离散的,但这些都不重要。重要的是,这片内存在一个更高层次上可以作为一个被计算机管理的区域,也就是我们形象化的对象。在这片内存中,存储着我们存放的数据或者代码。
在python中,对象就是C中结构体在堆上申请的一块内存。
在python中,对象一旦被创建,它在内存中的大小就是不变的。这就意味着那些需要容纳可变长度数据的对象只能在对象内维护一个指向一片可变的内存区域的指针。
为什么 要這样设计呢?
考虑一下这种场景,在内存中有对象A,在其后面紧跟着对象B,如果运行期某个时刻,A的大小增大,这就意味着必须将A整个移动到其它位置,否则,其增大的数据将会覆盖掉属于B对象的数据,这样就可能导致对象B所运行的程序崩溃。而如今,我们采用指针来指向一片区域,一旦A数据增大,我们只需在内存中找到一片足以容纳新增数据的内存块,将指针指向这片区域即可。
2.对象机制的灵魂-------PyObject
在python中,所有东西皆为对象,而所有对象都拥有一些相同的东西,这些东西都容纳在Pyobject中定义,它是python对象机制的核心。
[object.h]
typedef struct _object {
PyObject_HEAD ////python对象的秘密都隐藏在这个宏中
} PyObject; ////定长对象,如整数对象 整数对象占用内存是一样的,都是sizeof( PyIntObject)
#define PyObject_HEAD \ //这个是PyObject_HEAD的宏定义
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
#ifdef HAVE_SSIZE_T //这个是Py_ssize_t 的宏定义
typedef ssize_t Py_ssize_t; //有符号整数型
#elif SIZEOF_VOID_P == SIZEOF_SIZE_T
typedef Py_intptr_t Py_ssize_t; //int类型的指针
#else
# error "Python needs a typedef for Py_ssize_t in pyport.h."
#endif
最后简化一下pyobject就能看到其真正的面目:
typedef struct _object {
int ob_refcnt; \ //用于记录对对象的引用数
struct _typeobject *ob_type;
} PyObject; ////定长对象,如整数对象 整数对象占用内存是一样的,都是sizeof( PyIntObject)
从上面可以看出, PyObject包含两部分:一部分是整型变量ob_refcnt。这个变量与python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制。对于某一个对象A,当有一个新的pyobject*引用该对象,A的引用计数就会增加;而当这个pyobject*被删除是,A的引用计数就会减少。当引用计数减少到0时,A就会从堆上被删除,从而释放其使用的内存。
在python中,我们可以测试一下:
创建一个字符串对象并将其赋值给i,也就是i指向了这个字符串对象,调用函数,我们发现当前引用数位3,接着将其赋值给另外一个t变量,其引用计数增加1;删除事,我将t变量赋值为另外一个对象,发现'xxx'对象的引用计数减少了1,这与我们上面分析的一致。ob_refcnt正是计数这些对象的引用个数。同时我们也会发现,默认的引用计数数为3.
除此之外,上面结构体中还有另外一个变量ob_type,它是指向_typeobject 结构体的指针。这个指针对应着python内部的一中特殊对象,它是用来指定一个对象类型的类型对象。
在每一个Python对象中都必须有上述这些内容,而且这些内容将出现在没有python对所占内存的最开始字节。除了这些内容外,每个对象还有一些额外的内容,用来存放其他东西。
比如整型对象
[intobject.h]
typedef struct{
pyObject_HEAD
long ob_ival; //额外的long变量,用来存放值,如a = 5 这里的5就存放在这个变量中。
}PyIntObject;
3.定长对象和变长对象
整型变量,无论数值多么大,都会存放在ob_ival中。
但是像列表这样的变长对象,上述方法就不管用了。
在python中,也定义了一个变长对象。
typedef struct {
PyObject_VAR_HEAD
}
简化为:
typedef struct {
int ob_refcnt;
int ob_size; //ob_size指出变长对象中容纳了多少个元素,而不是字节数
struct _typeobject *ob_type;
} PyVarObject;
变长对象和定长对象的区别:定长对象的不同对象占用的内存大小是一样的;而变长对象不同对象占用的内存是不一样的。
所以,在变长对象中添加了一个变量ob_size,用来指明对象中容纳了多少个元素。
比如:list = [1,2,3,4] 其中有四个元素,那么ob_size = 4
对于任何的PyVarObject,其所占用的内存,开始部分的字节意义和pyobject是一样的。换句话说,每一个对象的拥有相同的对象头部。这就使得python中的对象引用就非常同意了,只需要一个pyobject*指针,就可以引用任意的一个对象。
4.类型对象
当我们创建一个对象,必然会为其分配内存。但是,不同对象所需内存大小是不一样的,那么python又是如何知道该分配多大内存呢?其实,这些内存大小的信息,就隐藏在对应的类型对象之中。
[object.h]
typedef struct _typeobject { PyObject_VAR_HEAD const char *tp_name; //类型字符名,如字符串类型'str' Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ //分配内存时使用 /* Methods to implement standard operations */ destructor tp_dealloc; //销毁 printfunc tp_print; //打印 getattrfunc tp_getattr; //获取属性 setattrfunc tp_setattr; //设置属性 cmpfunc tp_compare;// reprfunc tp_repr; /* Method suites for standard classes */ PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; /* More standard operations (here for binary compatibility) */ hashfunc tp_hash; ternaryfunc tp_call; reprfunc tp_str; getattrofunc tp_getattro; setattrofunc tp_setattro; /* Functions to access object as input/output buffer */ PyBufferProcs *tp_as_buffer; /* Flags to define presence of optional/expanded features */ long tp_flags; const char *tp_doc; /* Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ traverseproc tp_traverse; /* delete references to contained objects */ inquiry tp_clear; /* Assigned meaning in release 2.1 */ /* rich comparisons */ richcmpfunc tp_richcompare; /* weak reference enabler */ Py_ssize_t tp_weaklistoffset; /* Added in release 2.2 */ /* Iterators */ getiterfunc tp_iter; iternextfunc tp_iternext; /* Attribute descriptor and subclassing stuff */ struct PyMethodDef *tp_methods; struct PyMemberDef *tp_members; struct PyGetSetDef *tp_getset; struct _typeobject *tp_base; PyObject *tp_dict; descrgetfunc tp_descr_get; descrsetfunc tp_descr_set; Py_ssize_t tp_dictoffset; initproc tp_init; allocfunc tp_alloc; newfunc tp_new; freefunc tp_free; /* Low-level free-memory routine */ inquiry tp_is_gc; /* For PyObject_IS_GC */ PyObject *tp_bases; PyObject *tp_mro; /* method resolution order */ PyObject *tp_cache; PyObject *tp_subclasses; PyObject *tp_weaklist; destructor tp_del; /* Type attribute cache version tag. Added in version 2.6 */ unsigned int tp_version_tag; #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ Py_ssize_t tp_allocs; Py_ssize_t tp_frees; Py_ssize_t tp_maxalloc; struct _typeobject *tp_prev; struct _typeobject *tp_next; #endif } PyTypeObject;
上面信息主要包含四类:
类型名:tp_name,主要是python内部以及调试的时候使用
创建该类型对象时分配内存空间大小的信息,即tp_basicsize和tp_itemsize
与该类型对象相关的操作系统
类型的类型信息