前言
python是C语言实现的,因此 Python对象在C语言层面应该是一个 结构体 ,组织对象占用的内存。 不同类型的对象,数据及行为均可能不同,因此可以大胆猜测: 不同类型的对象由不同的结构体表示 。
对象也有一些共性,比如每个对象都需要有一个 引用计数 ,用于实现垃圾回收机制。因此,还可以进一步猜测: 表示对象的结构体有一个公共头部 。
一. 实例对象的基石—PyObject和PyVarObject编程客栈
PyObject和PyVarObject本质上是对象的 头部信息 。
1.1 PyObject结构体
Python对象都由 PyObject 结构体表示,对象引用则是指针 PyObject * 。 PyObject 结构体定义于头文件object.h,路径为 Include/object.h ,代码如下
typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject;
对结构体中的元素进行说明,
元素名称 说明 ob_refcnt 引用计数,对象被其他地方引用时加一,引用解除时减一; 当引用计数为零,便可将对象回收,这是最简单的垃圾回收机制。 ob_type 类型指针指向对象的类型对象,类型对象描述实例对象的数据及行为。 _PyObject_HEAD_EXTRA 宏,同样定义在Include/object.h头文件内。1.2 宏的定义
#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
如果 Py_TRACE_REFS 被定义,宏展开为两个指针 ob_next 和 ob_prev 用来实现双向链表。注释中说明,双向链表用于跟踪所有活跃堆对象,一般不启用,不深入介绍。
1.3 PyVarObject结构体
用于表示 变长对象 的 PyVarObject 结构体是在 PyObject 结构体的基础上加编程客栈入长度信息。
typedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ } PyVarObject;
相比 object 结构体增加了 ob_size 字段用于记录元素个数。
1.4 两种头部信息宏定义及其初始化
具体实例对象视其内存大小是否固定,决定其属于定长对象还是变长对象。相应的需要具有头部信息 PyObject 或 PyVarObject 。
因此,头文件准备了两个头部信息的宏定义 PyObject_HEAD 和 PyObject_VAR_HEAD ,方便对象使用,
#define PyObject_HEAD PyObject ob_base; #define PyObject_VAR_HEAD PyVarObject ob_base;
宏定义说明,
#define PyObject_HEAD PyObject ob_base; 表示将代码中其他出现PyObject_HEAD的地方,替换成PyObject ob_base;1.4.1 定长对象实现
内存大小固定的浮点数类的实现只需在 PyObject 头部基础上,用一个双精度浮点数double加以实现,
typedef struct { PyObject_HEAD double ob_fval; } PyFloatObject;
1.4.2 变长对象实现
内存大小不固定的列表对象则需要在 PyVarObject 头部的基础上,用一个动态数组加以实现,数组存储列表包含的对象,即 PyObject 指针,
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject;
PyListObject底层由一个数组实现,关键字段是以下3个,
字段 说明 ob_item 指向动态数组的指针,数组保存元素对象指针。 allocated 动态数组总长度,即列表当前的 容量。 ob_size 当前元素个数,即列表当前的 长度。列表容量不足时,Python会自动扩容,具体机制见list源码解读。
1.4.3 头部信息宏初始化PyObject_HEAD_INIT 用于 定长对象 头部信息初始化。将引用计数 ob_refcnt 设置为1并将对象类型 ob_type 设置成给定类型。
#define PyObject_HEAD_INIT(type) { _PyObject_EXTRA_INIT 1, type },
PyVarObject_HEAD_INIT 用于 变长对象 头部信息初始化。在前者基础上进一步设置长度字段 ob_size 。
#define PyVarObject_HEAD_INIT(type, size) { PyObject_HEAD_INIT(type) size },
在源码中经常见到这两个宏定义。
二. 类型对象的基石—PyTypeObject 2.1 PyTypeObject包含信息
PyObject 记录了Python中所有对象共有的信息。如引用计数、类型指针和变长对象特有的元素个数。但是还有一些细节需要考虑,
创建不同类型的对象时如何得知对象所需的内存信息 给定某个对象,如何判断它支持什么操作这些作为对象的 元信息 ,应该由一个独立实体保存,与对象所属类型密切相关。 PyObject 中包含的 ob_type 指针,指向一个 类型对象 。类型对象 PyTypeObject 也在 Include/object.h 中定义,关键字段如下,
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 */ /* Methods to implement standard operations */ destructor tp_dealloc; printfunc tp_print; getattrfunc tp_getattr; setattrfunc tp_setattr; // ... /* Attribute descriptor and subclassing stuff */ struct _typeobject *tp_base; // ...... } PyTypeObject;
类型对象 PyTypeObject 是一个变长对象,包含变长对象头 编程客栈 部信息 PyObject_VAR_HEAD 和专有字段,
字段 说明 类型名称 tp_name字段 类型的继承信息 tp_base字段指向基类对象 创建实例对象时所需的内存信息 tp_basicsize 和 tp_itemsize 字段 BxAJZpmU 该类型支持的相关操作信息 tp_print、tp_getattr等函数指针PyTypeObject 就是 类型对象 在 Python 中的表现形式,对应着面向对象中“类”的概念。 PyTypeObject 结构很复杂,目前只需要知道它保存着对象的元信息,描述对象的类型即可。
2.2 类型对象和实例对象在内存中的关系
以float为例,考察类型对象和实例对象在内存中的形态和关系,
>>> float <class 'float'> >>> pi = 3.14 >>> HdhCmsTestcppcns测试数据 e = 2.71 >>> type(pi) is float True
两个float 实例对象 都是 PyFloatObject 结构体 ,除了公共头部字段 ob_refcnt 和 ob_type ,专有字段 ob_fval 保存了对应的数值。 类型对象 是一个 PyTypeObject 结构体 ,保存了类型名、内存分配信息以及浮点数相关操作。 实例对象的 ob_type 字段指向类型对象 ,Python 据此判断对象类型,进而获悉关于对象的元信息。 float、pi以及e等变量只是一个指向实际对象的指针。
上图的内容并不完全正确,更深入的解读见后一篇博文。
到此这篇关于Python源码学习之PyObject和PyTypeObject的文章就介绍到这了,更多相关PyObject和PyTypeObject内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
查看更多关于Python源码学习之PyObject和PyTypeObject的详细内容...