Python源码学习之PyObject和PyTypeObject
作者:Jakob_Hu 发布时间:2023-08-11 10:28:45
前言
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 字段 |
该类型支持的相关操作信息 | tp_print、tp_getattr等函数指针 |
PyTypeObject
就是类型对象在 Python 中的表现形式,对应着面向对象中“类”的概念。PyTypeObject
结构很复杂,目前只需要知道它保存着对象的元信息,描述对象的类型即可。
2.2 类型对象和实例对象在内存中的关系
以float为例,考察类型对象和实例对象在内存中的形态和关系,
>>> float
<class 'float'>
>>> pi = 3.14
>>> e = 2.71
>>> type(pi) is float
True
两个float实例对象都是
PyFloatObject
结构体,除了公共头部字段ob_refcnt
和ob_type
,专有字段ob_fval
保存了对应的数值。类型对象是一个
PyTypeObject
结构体,保存了类型名、内存分配信息以及浮点数相关操作。实例对象的ob_type
字段指向类型对象,Python 据此判断对象类型,进而获悉关于对象的元信息。float、pi以及e等变量只是一个指向实际对象的指针。
上图的内容并不完全正确,更深入的解读见后一篇博文。
来源:https://blog.csdn.net/Jakob_Hu/article/details/117911858


猜你喜欢
- 一、实验目的实现学生选课系统二、实验环境Python3.6pymysql(Python连接MySQL)xlrd(操作Excel)三、程序结构
- 这两个均是 python 的内建函数,通过读取控制台的输入与用户实现交互。但他们的功能不尽相同。举两个小例子。>>> ra
- eval函数接收一个参数s,如果s不是字符串,则直接返回s。否则执行s语句。如果s语句执行结果是一个值,则返回此值,否则返回undefine
- 简洁的隐藏垂直菜单在hover时将内容展开。这样的效果在JS里有很多个版本,但这个可以说是绝无仅有的CSS版本。此菜单可以在IE5.5,IE
- 一、SQLalchemy简介SQLAlchemy是一个开源的SQL工具包,基本Python编程语言的MIT许可证而发布的对象关系映射器。SQ
- 本文实例讲述了Python实现检测文件MD5值的方法。分享给大家供大家参考,具体如下:前面介绍过Python计算文件md5值的方法,这里分析
- 简介pygame模块用于变换Surface,Surface变换是一种移动或调整像素大小的操作。所有这些函数都是对一个Surface进行操作,
- 第一步:下载mysql驱动cmd进入创建好的django项目目录:使用命令pip install mysqlclient等待安装
- ttk控件的width-height设置tkinter中窗口,框架容器,控件如果不设置宽度高度,则会根据其中的内容自动设置宽度高度框架容器设
- 保存Python程序,可以使用以下方法:使用编辑器编写代码并保存1、打开Notepad++2、需要新建文本时,点击左上角”文本“,在弹出的菜
- 起步在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作。例如,有一个类实例对象foo,它有一个name属性
- shelve类似于一个key-value数据库,可以很方便的用来保存Python的内存对象,其内部使用pickle来序列化数据,简单来说,使
- 环境准备数据库版本:MySQL 5.7.20-log建表 SQLDROP TABLE IF EXISTS `t_ware_sale_stat
- 看下这个 URLconf:from django.conf.urls.defaults import *from mysite.views
- 概要本文分步介绍了如何在运行 SQL Server 的计算机之间移动 Microsoft SQL Server 用户数据库和大多数常见的 S
- 1.创建主键语法 ALTER TABLE table_name ADD CONSTRAINT pk_name PRIMARY KEY(列名)
- 前言如果你认为深度学习非常的吃GPU,或者说非常的耗时间,训练一个模型要非常久,但是你如果了解了迁移学习那你的模型可能只需要几分钟,而且准确
- 三子棋的python实现代码,供大家参考,具体内容如下一、基本流程三子棋游戏实现逻辑如下:1、创建初始化3*3棋盘;2、玩家执U子,先进行落
- 说到排序,很多人可能第一想到的就是sorted,但是你可能不知道python中其实还有还就中方法哟,并且好多种场景下效率都会比sorted高
- Python SSH远程连接与文件传输from paramiko import (SSHClient, SFTPClient, AutoAd