《Python源码剖析》之对象的基石---PyObject

前言

在python的源代码中,PyObject的结构体定义如下,它的内容看起来很简单,只有3项,分别是:_PyObject_HEAD_EXTRAob_refcntob_type,其中_PyObject_HEAD_EXTRA是用于指向活动堆的指针,这个我们暂时不用管;ob_refcnt是用于引用计数的,它的类型是long,记录了当前对象被引用的次数;ob_type是对PyTypeObject类型的一个引用,它也是今天的一个主角,我稍后会在下面重点介绍它的,通过了解它,我相信你会发出类似于“哇!~”的感叹,或者是突然灵光一现的样子:原来是这样子🦆~(如果你也是一个python爱好者的话哈哈哈)

主角—PyObject登场

// object.h
/* Nothing is actually declared to be a PyObject, but every pointer to* a Python object can be cast to a PyObject*.  This is inheritance built* by hand.  Similarly every pointer to a variable-size Python object can,* in addition, be cast to PyVarObject*.*/
struct _object {_PyObject_HEAD_EXTRAPy_ssize_t ob_refcnt;PyTypeObject *ob_type;
};// pytypedefs.h
typedef struct _object PyObject;

在PyObject结构体上方注释有写到:Nothing is actually declared to be a PyObject, but every pointer to a Python object can be cast to a PyObject*. This is inheritance built by hand.这句话的大概意思就是:任何东西都不会被声明(实例化)为一个PyObject,但是任何指向python对象(int,str,float,dict等等)的指针都可以被映射到PyObject*,这是继承导致的。

看到这里,大家也许想到了一点什么?🤔此时,我们可以回想一下python中一些基础对象类它们的继承关系:如下图所示,通过访问每个类型的__mro__属性(ps:__mro__的全称是method resolution order,它记录了方法的调用顺序,也可以理解成类的继承顺序),发现,每一个类型最终都会继承一个object类,object自己除外;或者直接访问__base__属性,发现它们返回的也都是object类,除了object返回的是None。
image.243eca7cef4411eebf312b20f7a91591.png

看到这里,你也许心中已经有了一个非常明确的猜想:”object是任何类型的父类,或者说,python中的任何一个类都继承自object类,除了object自己外“!我相信你一定很确信这一点,但是为什么是这样子呢?为什么它的表现或者行为是这样子呢?在揭开这层薄薄的神秘的面纱之前,你始终无法确认你这个猜想为事实,如果你是一个求真务实的程序员的话。🙃

揭开面纱

当你开始探索这些对象的源码时,你将不需花费吹灰之力揭开这层薄薄的面纱!首先,我们来探索一下一个简单的对象—float,它的源码实现如下:

// floatobject.h
typedef struct {PyObject_HEADdouble ob_fval;
} PyFloatObject;// object.h
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   PyObject ob_base;

可以看到,非常的简单,它的结构体总共就两个元素,一个是PyObject_HEAD,这是一个宏定义,实际上就是一个pyObject对象,而这个PyObject正是上面的object!所以实际上float的结构体是包含了object的结构体的;另外一个就是ob_fval,根据它的类型,我们可以断定它是用来存储float对象的值的。为了肯定我们的答案,我们再来看一下其他类型的结构体定义:

// int类型的结构体定义
struct _longobject {PyObject_VAR_HEADdigit ob_digit[1];
};// PyObject_VAR_HEAD的定义
/* PyObject_VAR_HEAD defines the initial segment of all variable-size* container objects.  These end with a declaration of an array with 1
* element, but enough space is malloc'ed so that the array actually
* has room for ob_size elements.  Note that ob_size is an element count,
* not necessarily a byte count.*/#define PyObject_VAR_HEAD      PyVarObject ob_base;// PyVarObject的定义
typedef struct {PyObject ob_base;Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;// list类型的结构体定义
typedef struct {PyObject_VAR_HEAD/* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */PyObject **ob_item;/* ob_item contains space for 'allocated' elements.  The number* currently in use is ob_size.* Invariants:*     0 <= ob_size <= allocated*     len(list) == ob_size*     ob_item == NULL implies ob_size == allocated == 0* list.sort() temporarily sets allocated to -1 to detect mutations.** Items must normally not be NULL, except during construction when* the list is not yet visible outside the function that builds it.

可以看到int类型和list类型的结构体定义和float的有所不同,float包含的是PyObject_HEAD,但它们包含的却是PyObject_VAR_HEAD,不过我们继续探索发现,PyObject_VAR_HEAD实际上也是一个宏定义,它定义了一个PyVarObject类型的变量,变量名同样是ob_base
再进一步探索发现,PyVarObject是一个结构体,它里面也包含了object这个最基本的结构体!
所以,探索到这里,我们可以明确我们的猜想:任何对象都继承(c语言中用包含更合适)自object类型,是正确的!因此,将PyObject比做是python对象的基石也是无可厚非的!

:::tip
细心的同学可能发现不管是int类型还是float类型,它们都有定义一个名为ob_base的变量(不管它们的类型是否相同),但是object类型的结构体中没有,所以这也是上面访问__base__这个属性时,唯有object的值为None的原因。

:::

定长对象与变长对象

给对象分类是一个头疼的问题,因为根据不同的规则,可以分出不同的类型。如果你读过《流畅的python》,你可能还会记得其中关于“可变与不可变对象”这一分类的相关内容,其中关于tuple到底是可变还是不可变这个问题,始终有一种神秘感在里面哈哈哈🤪。

那么定长对象和变长对象又是根据什么规则来区分的呢?以及哪些是定长对象和变长对象呢?
答案其实就藏在上面👆的源码中。int类型和list类型最终都会包含object结构体,为什么非要绕那么一圈,或者说借助PyObject_VAR_HEAD来间接的包含它呢?

我们可以对比一下float对象和list对象在行为表现上的差异:float它只需要保存一个数值,是一个固定的对象;而list它保存的是n个对象,对象的数量是不确定的。发现了这一点差异,我们再回过头来看一下PyObject_VAR_HEAD的定义:

// PyObject_VAR_HEAD的定义
/* PyObject_VAR_HEAD defines the initial segment of all variable-size* container objects.  These end with a declaration of an array with 1
* element, but enough space is malloc'ed so that the array actually
* has room for ob_size elements.  Note that ob_size is an element count,
* not necessarily a byte count.*/#define PyObject_VAR_HEAD      PyVarObject ob_base;// PyVarObject的定义
typedef struct {PyObject ob_base;Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

它在开头就写到了PyObject_VAR_HEAD defines the initial segment of all variable-size container objects,意思是PyObject_VAR_HEAD定义了所有大小可变容器对象
的初始化片段。所以,只要包含了PyVarObject变量的对象都是容器对象,或者说是可变对象。我们常用的对象的分类如下:

  • 定长对象:float等
  • 变长对象,int,str,list,tuple等

int类型的对象是变长对象?

当我把int列在了变长对象的第一个位置时,你是否会感到疑惑?因为我们常用的int对象,它是如此的简单,仅仅用于存储一个整数值,怎么可能是变长对象呢?但是当你看到下面的源码时,你必须承认这个事实!但是,为什么要这么设计呢?区区一个整数,为什么还需要一个容器来存储?

我们不妨先从它的表现来入手,数值类型中,除了int以外,比较常用的就是float类型来,我们来对这两个对象做一些对比,看看有什么不同。
image.f91adf6afa4d11eebf312b20f7a91591.png
如上图所示,我们先实例化整数对象,然后将其转成float对象,第一次进行转换时,转换成功;第二次声明整数对象时成功,但是进行转换时就失败了,报错也很明显:就是这个数太大了无法转换成float对象。
此时,我们停止转换,再次继续声明更大的整数对象:

:::info
tips:float对象的最大取值大家可以探索源码去寻找。

:::
image.49e86060fa4f11eebf312b20f7a91591.png
可以看到,没有报错,说明声明成功!(因为数字太大,就没有打印),这看起来整数对象的大小没有限制,想申请多大就申请多大,实际上,就是这样的!看它下面的结构定义就知道啦!它的值没有直接存在一个地址中,而是分开存在一个名叫ob_digit的数组中,只要存储空间主够大,这个数组就能无限的开辟空间,这样就能存储足够大的整数了。

/*The absolute value of a number is equal to
SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
Negative numbers are represented with ob_size < 0;
zero is represented by ob_size == 0.
*/struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};

PyTypeObject

受篇幅限制,后面再专门写一篇博客来介绍吧。

更多内容可以关注博主的个人博客系统:《Python源码剖析》之对象的基石—PyObject

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/1064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习大数据,所需要的linux基础(1)

文章目录 linux入门概述Linux和Windows的区别CentOS下载地址 Linux文件与目录结构Linux文件Linux目录结构 VI/VIM编辑器vi/vim是什么测试数据集准备一般模式编辑模式指令模式模式间转换 网络配置和系统管理操作查看网络IP和网关配置网络和ip地址ifconfig配置网络接口修改ip地址…

使用Python进行自动化测试

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 如何使用Python进行自动化测试&#xff1a;测试框架的选择与应用 自动化测试是软件开发过程…

curlftpfs和fusermount

curlftpfs 是一种 Linux 系统下用来将 FTP 服务器挂载为文件系统的工具&#xff0c;这意味着可以通过本地目录来访问和操作 FTP 服务器上的文件。 挂载FTP服务器到本地系统 为了挂载FTP服务器到本地系统中&#xff0c;使用curlftpfs工具&#xff0c;可以按照以下格式书写命令…

保姆级教程!QRCNN-BiLSTM一键实现多变量回归区间预测!区间预测全家桶再更新!

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 今天对我们之前推出的区间预测全家桶进行…

进程间通信IPC(二)

一、存储映射I/O(Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是从缓冲区中取数据&#xff0c;就相当于读文件中的相应字节。与此类似&#xff0c;将数据存入缓冲区&#xff0c;则相应的字节就自动写入文件。这样&#xff0c;就可在不使用read和write…

由于找不到msvcp110d.dll,无法继续执行代码

在计算机软件开发和运行环境中&#xff0c;动态链接库&#xff08;DLL&#xff09;文件扮演着至关重要的角色。它们封装了特定功能的代码&#xff0c;使得多个应用程序能够共享这些功能而无需重复编译或加载相同的代码&#xff0c;从而显著提升了系统资源利用率和软件开发效率。…

024——驱动、server、client、GUI全功能联调

目录 一、本次修改 二、GUI和Client之间联调 2.1 工程结构修改 2.2 将TCP程序修改为可被其它程序调用 2.3 优化显示界面 2.4 解决GUI通过tcp send的问题 2.5 处理服务器数据 时间不是很多了&#xff0c;我想压缩一下快点把属于毕设的这部分搞完&#xff0c;俺要出去旅游…

【HTML】H5新增元素记录

H5 新增元素特性 1. 语义化标签 语义化标签的好处&#xff1a; 对于浏览器来说&#xff0c;标签不够语义化对于搜索引擎来说&#xff0c;不利于SEO的优化 语义化标签&#xff1a; header:头部元素nav&#xff1a;导航section:定义文档某个区域的元素article:内容元素aside…

解锁多智能体路径规划新境界:结合启发式搜索提升ML本地策略

引言&#xff1a;多智能体路径寻找&#xff08;MAPF&#xff09;问题的重要性与挑战 在现代自动化和机器人技术迅速发展的背景下&#xff0c;多智能体路径寻找&#xff08;Multi-agent path finding&#xff0c;简称MAPF&#xff09;问题的研究变得日益重要。MAPF问题涉及为一…

【NTN 卫星通信】NTN的SSB波束探讨

1 概述 SSB是同步广播信道&#xff0c;用于小区搜索&#xff0c;主系统消息的发送。NR协议中定义了多种SSB波束格式&#xff0c;简述如下。   小区搜索是终端获取与小区的时间和频率同步并检测小区的物理层小区ID的过程。   为了进行小区搜索&#xff0c;UE接收以下同步信号…

MySQL Workbench下载安装、 MySQL Workbench使用

官方下载链接;MySQL :: Download MySQL Workbench 下载好懒人安装&#xff0c;也可自己选择目录 下面是使用&#xff1a; 连接数据库&#xff1a; 填写数据库连接信息&#xff1a; 基本操作部分&#xff1a; 数据导入导出&#xff1a; 导出/备份 导入&#xff1a; 生产er图…

【热门话题】探索与心得:深入体验Microsoft Edge浏览器

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 探索与心得&#xff1a;深入体验Microsoft Edge浏览器一、Edge浏览器概述1.1 发…

大型网站系统架构演化实例_5.使用反向代理和CDN加速网站响应

1.使用反向代理和CDN加速网站响应 随着网站业务不断发展&#xff0c;用户规模越来越大&#xff0c;由于区域的差别使得网络环境异常复杂&#xff0c;不同地区的用户访问网站时&#xff0c;速度差别也极大。有研究表明&#xff0c;网站访问延迟和用户流失率正相关&#xff0c;网…

【嵌入式】交叉编译指南:将开源软件带到嵌入式世界

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

mybatis一对一,多对一,一对多--使用自动映射避免繁琐的resultMap

头疼的一对一&#xff0c;多对一&#xff0c;一对多写法 我们知道&#xff0c;相比较hibernate,mybatis的一对一&#xff0c;一对多都比较繁琐&#xff0c;hibernate可以直接在实体类里面配置好映射关系&#xff0c;获取值的时候就能把一对一和一对多的对象带出来了&#xff0…

手把手教你实现贪吃蛇

前言 在实现贪吃蛇前&#xff0c;我们需要熟练地掌握C语言知识&#xff0c;对初阶数据结构中的链表有一定的掌握&#xff0c;并且我们还会使用到Win 32 API 的知识&#xff0c;下面我会对需要使用到的API接口函数进行解释。最终的代码我放在后面&#xff0c;有需要的可以自取。…

探索C语言数据结构:利用顺序表完成通讯录的实现

在好久之前我就已经学习过顺序表&#xff0c;但是在前几天再次温习顺序表的时候&#xff0c;我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能&#xff0c;那么今天就来好好通过博客总结一下通讯录如何完成吧。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留…

OpenHarmony其他工具类—lua

简介 Lua是一种功能强大、高效、轻量级、可嵌入的脚本语言。 支持过程编程、面向对象编程、函数编程、数据驱动编程和数据描述。 下载安装 直接在OpenHarmony-SIG仓中搜索lua并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 将下载的lua库代码存在以下路径&#…

Java Web3-2 - tomcat

https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Tomcat_架构解析.md https://zhuanlan.zhihu.com/p/40249834 早期&#xff0c;web技术主要用于浏览静态页面 时间发展&#xff0c;用户已经不满足于仅浏览静态页面。用户需要一些交互操作&#xff0c;获取…

STM32G431RBT6之时钟树配置与生成工程

默认大家都下载了蓝桥杯嵌入式资源包了哈. 首先,打开cubumx,修改RCC与SYS. 打开并观察原理图,发现晶振是24Mhz. 第一步,打开Clock Configuration. 第二步,修改晶振为原理图相对应的24Mhz. 第三步,切换到HSE. 第四步,切换到PLLCLK. 第五步,设置HCLK为80Mhz(15届真题要求为8…