目录
1.对象机制的基石PyObject
2.可变对象类型基石:PyVarObject
3.类的设计核心PyTypeObject
3.1.对象的创建原理
3.2 对象行为
3.3 类型的类型
4.对象的多态/动态语言的原理
5.计数引用
在python中,很多模块都是直接由c实现的,所有东西都是对象,即使是int,float等这样的基本数据类型,都是一个对象,比如
>>> import sys
>>> print(sys.getsizeof(int))
416
>>>
一个int就占用416比特,正是因为python对所有东西都封装成了对象,所以python程序运行需要占用的内存就比c,c++,java等要多得多。
在接下来的说明中,一切都参照python3.7.5源码作讲解。
在python中,所有东西都是对象,所有对象拥有相同的部分内容,而这内容就是PyObject,所以对象的核心是PyObject。
我们先看看PyObject的定义:
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
ob_refcnt其实是一个计数器,与内存管理机制有关,对于对象A,当 新PyObject*引用A对象时,A的计数器+1,当删除这个PyObject*时A的计数器-1,当A计数器为0时,A将从堆中被回收。
而ob_type是指明一个对象类型的类型对象,_typeobject结构体是python中内部特殊的对象。
所以在python中,对象机制的核心非常简单:引用计数器+类型信息
PyObject是所有类型对象都必须拥有的,它必须放在对象内存占用最开始的那个字节,除了PyObject,还可以新增额外的信息,构成其他类型的对象,比如float类型的结构设计是:
typedef struct {
PyObject_HEAD //PyObject_HEAD定义每个PyObject的初始段。
double ob_fval;
} PyFloatObject;
我们可以看出,float除了PyObject,还有而外的double变量,ob_fval用来存放数值。同理,list,map,set,str等所有对象也是如此:在PyObject之外还定义属于自身的特殊信息。
定长对象:拥有固定大小,如int,float等
变长对象:可变元素个数,如str,list等
我们知道,在c 中没有诸如java,python,c++的字符串对象,准确来说,字符串对象维护的是多个char变量,换句话说,python中字符串对象维护的是n个PyObject对象,类似list,dict等对象拥有共同的特征:“n个PyObject”,于是乎就有了PyObject的扩展PyVarObject,它的结构体设计:
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* 可变部分的对象数 */
} PyVarObject;
ob_size记录的是变长对象中容纳了几个元素,比如list有5个元素,那么ob_size就等于5.
每个对象头部都有统一的PyObject,这使得对对象的引用变得非常统一,我们只需一个PyObject* 指针就可以指向任意一个对象。
一个类固然会包含大小,函数等信息,那它在python是怎么设计的呢?下面是类的设计源码
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "." */
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;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
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 */
unsigned 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;
/* 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;
destructor tp_finalize;
#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;
上面的源码我们可以看出:类定义包括了
>>> __builtins__.__dict__['int']
>>> int.__base__
>>>
从这段代码我们可以看出,int类型的基类是object,int对应python内部的PyInt_Type,object对应PyBaseObject_Type,下面使用c++中int类型的创建在python内部的创建过程
int(10)创建过程:首先调用PyInt_Type中的tp_new,如果tp_new为null(实际不可能为null的),那么会到tp_base对应的基类PyBaseObject_Type调用的tp_new,由于该tp_new指向object中的object_new,在object_new中,会访问PyInt_Type中的tp_basicsize信息(确定类需要占用内存的大小),继而完成申请内存操作,完成(创建对象)之后转向调用PyInt_Type的tp_init完成初始化操作。其中tp_new相当于c++中的new,tp_init相当于构造函数。
PyTypeObject定义了大量的函数指针,这些指针最终指向某个函数或null,可以被视为类型对象中所定义的操作,而这些操作直接决定一个对象所表现出的行为。
比如其中的tp_hash:指明该类型的对象如何生成hash值,tp_new 指明该类型对象如何申请内存空间,tp_init指明该类型对象如何初始化。
在这里还要说说三组非常重要的操作族:
从PyTypeObject的设计可以看出对于一种对象来说,可以同时包含有上面三种操作族,也就是说,一个对象可以表现出数值对象的特性,也可以表现出序列和关联对象的特性,举例:
>>> class A(int):
def __getitem__(self, item):
return item + str(self)
>>> a = A(1)
>>> b = A(2)
>>> print(a+b)
3
>>> a['key']
'key1'
>>>
A是从int继承出来的,应该就是一个数值对象,但是a['key']却表现出来关联对象才有的特性,这是因为通过重写__getitem__这个特殊方法(可以视为指定了A在python内部对应的PyTypeObject对象的tp_as_mapping操作)。
在PyTypeObject定义的最开始,我们可以发现PyObject_VAR_HEAD(定义所有可变大小容器对象的初始段),这意味着python中的类型也是一个对象,那么类型对象的类型是什么呢?这个问题十分重要,对于其他对象,可以通过与其关联的类型对象确定其类型,答案是PyType_Type,先看看它的结构:
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
........
}
PyType_Type是python中至关重要的对象,所有自定义对象都是通过它创建的,它就是type元类。
以PyInt_Type为例,可以清晰看出一般对象与PyType_Type的关系:
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type, 0)
"int", /* tp_name */
sizeof(PyIntObject),
、 ........
}
在python中创建一个对象,比如创建PyIntObject对象时,会分配内存,进行初始化,然后使用PyObject*来维护对象,而不是PyIntObject*。其他对象也是如此,所以在python内部的各个函数之间传递的都是一种范型指针PyObject*,我们不能知道指针所指的究竟是哪种类型,只有通过指针所指对象的ob_type域动态确定,正是通过这个域,python实现了多态机制,这也解释了python为什么是动态语言
python通过对一个对象的引用计数的管理来维护对象在内存中存在与否,每个对象都有一个ob_refcnt变量,这个变量维护着该对象的引用计数,从而决定该对象的创建与消亡。在python中,主要是通过Py_INCREF(op) 和Py_DECREF(op) 两个宏来增加和减少计数。
#define _Py_Dealloc(op) ( \
_Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA \
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
#endif
#endif /* !Py_TRACE_REFS */
#define Py_INCREF(op) ( \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
((PyObject *)(op))->ob_refcnt++)
#define Py_DECREF(op) \
do { \
PyObject *_py_decref_tmp = (PyObject *)(op); \
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \
--(_py_decref_tmp)->ob_refcnt != 0) \
_Py_CHECK_REFCNT(_py_decref_tmp) \
else \
_Py_Dealloc(_py_decref_tmp); \
} while (0)
#define Py_CLEAR(op) \
do { \
PyObject *_py_tmp = (PyObject *)(op); \
if (_py_tmp != NULL) { \
(op) = NULL; \
Py_DECREF(_py_tmp); \
} \
} while (0)
/* Macros to use in case the object pointer may be NULL: */
#define Py_XINCREF(op) \
do { \
PyObject *_py_xincref_tmp = (PyObject *)(op); \
if (_py_xincref_tmp != NULL) \
Py_INCREF(_py_xincref_tmp); \
} while (0)
#define Py_XDECREF(op) \
do { \
PyObject *_py_xdecref_tmp = (PyObject *)(op); \
if (_py_xdecref_tmp != NULL) \
Py_DECREF(_py_xdecref_tmp); \
} while (0)
从源码可以知道,当计数减到0时,Py_DECREF将调用_Py_Dealloc来释放内存资源,这个析构动作是通过对象对应的类型对象中定义的一个函数指针tp_dealloc来实现。
另外,频繁的申请和释放内存对执行效率的影响非常大,所以在python大量采用内存对象池来避免频繁的申请和释放内存空间,因此在析构时,通常是将对象占用的空间归还到内存池中。