面向对象理论中“类”和“对象”这两个重要概念在python中均以对象的形式存在。
“类”是一种对象,称为类型对象;“类”实例化出来的“对象”也是对象,称为实例对象。根据上节的分析,可以根据对象的特点进一步划分:
所以对象在python长得什么“妖怪模样”?
由于python是由C语言实现的,因此python对象在C语言层面应该是一个结构体。不同类型的对象,数据以及行为均可能存在差异,这是对象的个性,对象也是存在一些共性的,比如每个对象都需要有一个引用计数,用于实现垃圾回收机制。
关于对象的具体模样,需要在源码中一窥究竟
python doc about object
PyObject,对象的基石 在python内部,定长对象 都是由PyObject结构体表示,对象引用则是利用指针 PyObject * 。关于PyObject结构体的定义
位于源码位置/include/object.h
1 2 3 4 5 6 7 8 9 10 typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject;
PyObject结构体的成员有三:
宏定义_PyObject_HEAD_EXTRA
1 2 3 4 5 6 7 8 9 10 11 12 #ifdef Py_TRACE_REFS #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
若Py_TRACE_REFS有定义,则宏_PyObject_HEAD_EXTRA展开为两个_object指针,注释:这两个指针构成双向链表由于串起所有堆中活跃对象,一般不启用,不做过多介绍
引用计数(ob_refcnt)
当对象在其他位置被引用,该值+1,在某处引用解除,该值-1,当引用计数 = 0,则由回收机制进行资源回收。
类型指针(ob_type)
指明当前对象所属的类型,由此指向的类型决定当前对象的描述,数据,以及对象可进行的操作
变长对象 是在定长对象PyObject基础上扩充的PyVarObject定义(/include/object.h)
1 2 3 4 typedef struct { PyObject ob_base; Py_ssize_t ob_size; } PyVarObject;
结构体PyVarObject成员有二:
第一个成员ob_base是上文提及的PyObject类型
相比PyObject多了一个成员ob_size,用于说明串联的元素个数ob_size,比如int类型,需要串联几个32位整型进行数值表达
python根据对象的特点(是否定长)的需要来包含相应的头部PyObject 、 PyVarObject。为此,为这两个头部准备了宏定义,方便其他对象使用
1 2 #define PyObject_HEAD PyObject ob_base; #define PyObject_VAR_HEAD PyVarObject ob_base;
定长对象举例–float 对于定长对象 float对应的结构体PyFloatObject(定义位置:/include/floatobject.h)
1 2 3 4 typedef struct { PyObject_HEAD double ob_fval; } PyFloatObject;
变长对象举例–list 变长对象list对应的结构体PyListObject(定义位置:/include/listobject.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef Py_LIMITED_API typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject; #endif
根据注释内容对ob_item的描述,ob_item包含的空间是'allocated' elements
而allocated element
中已使用的部分由ob_size
进行描述,ob_size的位置是可变对象头部PyObject_VAR_HEAD中的字段,这里再贴一遍PyObject_VAR_HEAD的结构体:
1 2 3 4 typedef struct { PyObject ob_base; Py_ssize_t ob_size; } PyVarObject;
因此,对于变长对象list的示意图:
可以看到,对于变长对象list而言,在结构体中会设置分配的空间allocated大小,随后若list空间不足需求,会有相应的扩充方法,这部分后续学习到list源码在做补充吧。
对象头部的初始化 python为了方便对对象头部初始化做了相关宏定义,这里做下拆解进行说明
对于定长对象 代码位置(/include/object.h)
1 2 3 #define PyObject_HEAD_INIT(type) \ { _PyObject_EXTRA_INIT \ 1, type },
PyObject_HEAD_INIT一般用于定长对象头部初始化,对于定长对象头部:
1 2 3 4 5 typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type ; } PyObject;
可以看到初始化操作设置_PyObject_HEAD_EXTRA使用宏_PyObject_EXTRA_INIT 由于_PyObject_HEAD_EXTRA一般不启用,不做深究。
接着设置定长对象的引用计数 = 1,并根据定长对象的类型完成ob_type类型的设置。
变长对象 接着看变长对象初始化做了什么
1 2 #define PyVarObject_HEAD_INIT(type, size) \ { PyObject_HEAD_INIT(type) size },
同样的,这里再po一遍变长对象 头部:
1 2 3 4 typedef struct { PyObject ob_base; Py_ssize_t ob_size; } PyVarObject;
ob_base前文提到是定长对象头部的宏定义来的,为了方便引用。相比定长对象头部,只是多了一个成员ob_size。
对于变长对象头部中的定长对象头部,做的初始化和定长对象头部初始化一致,设置对象引用计数 = 1,完成对象类型ob_type的设置,最后对变长对象头部独有的长度size设置。
PyTypeObject,类型对象的基石 这部分关乎上文提到的PyObject(PyVarObject)头部中的type类型设置。
经过上述PyObject对象相关学习,知道对于对象头中一些字段是所有对象共有的,引用计数、类型指针..也有一些值区别字段,比如变长对象中指示元素格式的ob_size
目前对于python对象经过前面的学习,知道了int和 list二者都是变长对象,先看下面代码
1 2 3 4 5 6 7 8 9 a = [] print (sys.getsizeof(a))b = 1 print (sys.getsizeof(b))56 28
可以看到,同为变长对象的int list二者在分配内存有所差异的,再比如对于list对象,我们可以使用append追加元素的函数,而对于int类型则不行,也就是创建对象初始化时做的比我们想的要多,那么依据又是什么呢? –答案已经显而易见的,根据实际创建的对象所对应的类型而言,我们注意到在对象头部初始化:
1 2 3 #define PyObject_HEAD_INIT(type) \ { _PyObject_EXTRA_INIT \ 1, type },
顺藤摸瓜,找到PyObject头部中查看type对应的结构体:**_typeobject**
找到定义的位置(/Doc/includes/typestruct.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 typedef struct _typeobject { PyObject_VAR_HEAD const char *tp_name; Py_ssize_t tp_basicsize, tp_itemsize; destructor tp_dealloc; printfunc tp_print; getattrfunc tp_getattr; setattrfunc tp_setattr; PyAsyncMethods *tp_as_async; reprfunc tp_repr; PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; hashfunc tp_hash; ternaryfunc tp_call; reprfunc tp_str; getattrofunc tp_getattro; setattrofunc tp_setattro; PyBufferProcs *tp_as_buffer; unsigned long tp_flags; const char *tp_doc; traverseproc tp_traverse; inquiry tp_clear; richcmpfunc tp_richcompare; Py_ssize_t tp_weaklistoffset; getiterfunc tp_iter; iternextfunc tp_iternext; 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; inquiry tp_is_gc; PyObject *tp_bases; PyObject *tp_mro; PyObject *tp_cache; PyObject *tp_subclasses; PyObject *tp_weaklist; destructor tp_del; unsigned int tp_version_tag; destructor tp_finalize; } PyTypeObject;
从结构体的定义来看,关键信息在于:
PyTypeObject第一个成员为PyObject_VAR_HEAD,可见PyTypeObject是一个变长对象
tp_name成员,类型名称,可用于打印需要
事关内存分配的 tp_basicsize, tp_itemsize
类型的继承信息,例如tp_base指向基类对象
类型的子类信息,tp_subclasses
类型允许的操作信息:descrgetfunc tp_descr_get;等等
PyTypeObject 类型就是类型对象在python中的表现形式,对应着面向对象中“类”的概念
PyList_Type 实例进行探讨,以list类型为例:
1 2 3 4 5 6 7 8 9 10 var = ['a' ,'b' ] print (type (var))var.append('c' ) var var2 = [1 ,2 ,3 ] print (type (var2))<class 'list' > <class 'list' >
list 为列表类型对象,在系统中只有唯一一个,保存着所有列表实例出来的实例对象的元信息(共性部分 )。
由于list类型对象全局唯一,在C语言层面作为一个全局静态变量静态定义即可。python也是怎么做的
顺藤摸瓜:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 PyTypeObject PyList_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0 ) "list" , sizeof (PyListObject), 0 , (destructor)list_dealloc, 0 , 0 , 0 , 0 , (reprfunc)list_repr, 0 , &list_as_sequence, &list_as_mapping, PyObject_HashNotImplemented, 0 , 0 , PyObject_GenericGetAttr, 0 , 0 , Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, list___init____doc__, (traverseproc)list_traverse, (inquiry)_list_clear, list_richcompare, 0 , list_iter, 0 , list_methods, 0 , 0 , 0 , 0 , 0 , 0 , 0 , (initproc)list___init__, PyType_GenericAlloc, PyType_GenericNew, PyObject_GC_Del, };
至此可以看到一些list类型对象的一些个性行为。比如list_methods,跟随:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 tatic PyMethodDef list_methods[] = { {"__getitem__" , (PyCFunction)list_subscript, METH_O|METH_COEXIST, "x.__getitem__(y) <==> x[y]" }, LIST___REVERSED___METHODDEF LIST___SIZEOF___METHODDEF LIST_CLEAR_METHODDEF LIST_COPY_METHODDEF LIST_APPEND_METHODDEF LIST_INSERT_METHODDEF LIST_EXTEND_METHODDEF LIST_POP_METHODDEF LIST_REMOVE_METHODDEF LIST_INDEX_METHODDEF LIST_COUNT_METHODDEF LIST_REVERSE_METHODDEF LIST_SORT_METHODDEF {NULL , NULL } };
从PyList_Type的构成可以看到,用于初始化list类型对象头部的语句是:PyVarObject_HEAD_INIT(&PyType_Type, 0)
可以看到使用&PyType_Type
初始化变长对象头部中的定长对象头部分(因为这是对象共有部分)。
PyType_Type 前面提到。
进一步探讨PyType_Type
,跟踪,来到/Objects/typeobject.c,实际上这个结构体就是类型变量的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0 ) "type" , sizeof (PyHeapTypeObject), sizeof (PyMemberDef), (destructor)type_dealloc, 0 , 0 , 0 , 0 , (reprfunc)type_repr, 0 , 0 , 0 , 0 , (ternaryfunc)type_call, 0 , (getattrofunc)type_getattro, (setattrofunc)type_setattro, 0 , Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS, type_doc, (traverseproc)type_traverse, (inquiry)type_clear, 0 , offsetof(PyTypeObject, tp_weaklist), 0 , 0 , type_methods, type_members, type_getsets, 0 , 0 , 0 , 0 , offsetof(PyTypeObject, tp_dict), type_init, 0 , type_new, PyObject_GC_Del, (inquiry)type_is_gc, };
python内建类型和自定义类对应的PyTypeObject对象都是通过PyType_Type创建的。PyType_Type在python类型机制中是一个至关重要的对象,他是所有类型的类型,称为元类型 ,类型也是对象,其类型指向自身,这一点可以在上述代码中PyVarObject_HEAD_INIT(&PyType_Type, 0)
得到印证,因为将其自身作为ob_type的值
PyBaseObject_Type,类型之基 object是另一个特殊的类,他是所有类型的基类 ,在所有类型的结构体中由字段tp_base
指明
PyList_Type.tp_base:
发现相应的字段并没有进行初始化。根据理论,这个值不应该为0,因为他是由一个基类派生出来的。因此应该是在源码的某处完成了该字段的初始化,查找PyList_Type发现几处出现,这里需要记住一些源码阅读的技巧:
尽管查找结果有许多处,但首先可以关注文件名,进一步关注内容 ,从而定位到/Objects/object.c中的代码:
1 2 3 4 5 6 7 8 void _Py_ReadyTypes(void ) { if (PyType_Ready(&PyList_Type) < 0 ) Py_FatalError("Can't initialize list type" ); }
原来是需要进一步调用PyType_Ready
完成最终的初始化是python/C API,文档相关描述:
int PyType_Ready
(PyTypeObject *type )
Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0
on success, or return -1
and sets an exception on error.
跟踪该函数,来到/Objects/tyoeobject.h中找到函数的定义:代码有点多,多是一些其他字段的设置,这里先关注类型基类的设置部分
1 2 3 4 5 6 7 8 9 10 11 12 int PyType_Ready (PyTypeObject *type) { base = type->tp_base; if (base == NULL && type != &PyBaseObject_Type) { base = type->tp_base = &PyBaseObject_Type; Py_INCREF(base); } }
当tp_base为null且类型不是PyBaseObject_Type时,设置基类指向PyBaseObject_Type。即设置所有非PyBaseObject_Type类型的类型中的tp_base指向PyBaseObject_Type(好绕…),不管如何,大致意思是PyBaseObject_Type是所有类型的基类型,跟踪其定义,来到/Objects/tyoeobject.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0 ) "object" , sizeof (PyObject), 0 , object_dealloc, 0 , 0 , 0 , 0 , object_repr, 0 , 0 , 0 , (hashfunc)_Py_HashPointer, 0 , object_str, PyObject_GenericGetAttr, PyObject_GenericSetAttr, 0 , Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, PyDoc_STR("object()\n--\n\nThe most base type" ), 0 , 0 , object_richcompare, 0 , 0 , 0 , object_methods, 0 , object_getsets, 0 , 0 , 0 , 0 , 0 , object_init, PyType_GenericAlloc, object_new, PyObject_Del, };
其中 0, /* tp_base */
可以看到类型基类的PyBaseObject_Type其基类为空,这是因为所有基类到这里就是终点,需要一个出口,否则陷入定义的死循环。
可以总结出来的是:
所有类型的元类型是PyType_Type
(类型的ob_type指向PyType_Type),
所有类型的基类是PyBaseObject_Type
(非PyBaseObject_Type类型的tp_base指向PyBaseObject_Type)
最后补一个十分详细的PyObject 个字段的描述:Link