python源码剖析之PyObject详解


Posted in Python onMay 18, 2021

一、Python中的对象

Python中一切皆是对象。
————Guido van Rossum(1989)

这句话只要你学过python,你就很有可能在你的Python学习之旅的前30分钟就已经见过了,但是这句话具体是什么意思呢?

一句话来说,就是面向对象中的“类”和“对象”在Python中都是对象。类似于int对象的类型对象,实现了“类的概念”,对类型对象“实例化”得到的实例对象实现了“对象”这个概念。

通常的说法是,对象是数据以及基于这些数据的操作的集合。在计算机上,一个对象实际上就是一片被分配的内存空间,这些内存可能是连续的,也有可能是离散的,这都不重要,重要的是这片内存在更高的层次上可以作为一个整体来考虑,这个整体就是一个对象。在这片内存中,存储着一系列的数据以及可以对这些数据进行修改或读取的一系列操作的代码。

在 Python 中,对象就是在堆上申请的结构体,对象不能是被静态初始化的,并且也不能是在栈空间上生存的。唯一的例外就是类型对象(type object),Python中所有的类型对象都是被静态初始化的。在 Python 中,一个对象一旦被创建,它在内存中的大小就是不变的了。 这就意味着那些需要容纳可变长度数据的对象只能在对象内维护一个指向一个可变大小的内存区域的指针。

1.1 对象机制的基石PyObject

PyObjectPyVarObject分别表示定长对象和变长对象,使用的C的struct实现的,在结构中分别只定义了 PyObject_HEADPyObject_VAR_HEAD,后者仅仅是前者加上一个表示容纳元素个数的ob_size

[object.h]
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \
	_PyObject_HEAD_EXTRA \
	int ob_refcnt; \
	struct _typeobject *ob_type;

#define PyObject_VAR_HEAD \
	PyObject_HEAD \
	int ob_size; /* Number of items in variable part */

而对于两者共有的PyObject_HEAD中,只有两个东西,一个是维护引用计数的ob_refcnt和一个指向类型对象PyTypeObject结构体的指针。因此, Python 中实际上对象机制的核心非常的简单,一个是引用计数,一个就是类型。并且Python中每一个对象的开始字节都是相同的头部,这使得对Python对象的引用十分统一,只需要一个PyObject*可以引用任意一个对象。

python源码剖析之PyObject详解

这两个结构体定义的只是Python中对象共有的部分,其他的具体类型会有额外的结构体来定义,否则的话所有的对象岂不是都一样了?比如int类型的结构体定义PyIntObject中包含了PyObject_HEADob_ival后者是一个long,存放具体的值。

二、类型对象

那初始化对象的时候,去那里获得对象的大小呢?只能是在类型对象PyTypeObject中了!类型对象中存放了大量对象的元信息,大小显然是一种和对象的类型有关的元信息!注意,一个PyObject对象就是Python中对面向对象理论中类这个概念的实现,这里面存放了类型名、内存空间、操作函数指针等信息。

2.1 对象的创建

Python会用两种方法创建对象,一种是泛型API(AOL:Abstract Object Layer),可以应用在任何Python对象上,API内不会有机制确定最终调用哪个具体函数,比如PyObject_New(PyObject, &PyInt_Type)。另一种是类型相关API(COL:Concrete Object Layer),只能应用于具体类型的对象上,比如PyInt_FromLong(10)

自定义对象在Python内部不可能存在COL,所以只能根据其类型对象来创建实例对象,这就需要PyTypeObject中的tp_new函数指针,如果是自定义对象,这个指针可能是空,那就通过PyTypeObjecttp_base找到类型对象的基类,再找tp_new指针,这个过程中会利用类型对象中记录的空间信息申请内存。对于 Python 中的任何一种变长对象,tp_itemsize 这个域是必须设置的,tp_itemsize 指明了由变长对象保存的元素的单位长度,所谓单位长度即是指一个对象在内存中的长度。这个 tp_itemsizeob_size 共同决定了应该额外申请的内存的总大小是多少。

内建对象最终会使用COL完成创建工作。

new函数完成后,会执行init函数,前者类似于new操作符,后者类似于构造函数。

python源码剖析之PyObject详解

2.2 对象的行为

像前面说的,对象的行为被类型对象中的函数指针所定义。这些操作中,有三组非常重要的操作族:tp_as_numbertp_as_sequencetp_as_mapping分别指向PyNumberMethodsPtSequenceMethodsPyMappingMethods函数族结构体。所谓“鸭子类型”,就行能找到该类型对应的操作,就可以当做这个类型来用。

class MyInt(int):
    def __getitem__(self, key):
        return key+str(self)

a=MyInt(1)
b=MyInt(2)
print(a+b)
print(a['somekey'])

可以发现通过int继承得到的数值对象,通过重写魔术方法,使其支持了字典类型的操作,其实我们可以认为是,制定了MyInt这个类型对象tp_as_mapping.mp_subscript操作。

2.3 类型的类型

之前说了,作为类的实现的类型对象也是Python对象,那么类型对象PyObjectob_type指针指向哪呢?是指向自己吗?尽管我一开始也是这么想的,但可惜这个答案不对,类型对象指向的对象是PyType_Type。这个对象在Python类型机制中很重要,所有用户自定义class的PyTypeObject对象都是通过这个对象创建的,因此他是Python中的元类(metaclass)。他是所有class的class。而元类的类型是自己,这里出现了我们一开始认为会出现的自己只想自己的情况!

i=1
class A:
    pass
a=A()
print(i.__class__) # 类型对象
print(i.__class__.__class__) # 元类
print(a.__class__) # 类型对象
print(a.__class__.__class__) # 元类
print(a.__class__.__class__.__class__) # 指向自己

python源码剖析之PyObject详解

留在这里的疑问:虚线和虚线指向的对象是啥玩?

三、Python的多态性

通过 PyObject 和类型对象, Python 利用 C 语言完成了 C++所提供的继承和多态的特性。一开始已经提到,Python中所有对象的前面几个字节都是PyObject类型也就是PyObject_HEAD结构体。因此在 Python 内部各个函数之间传递的都是一种范型指针PyObject*。这个指针所指的对象究竟是什么类型的,不知道,只能从指针所指对象的ob_type域判断,而正是通过这个域,Python 实现了多态机制。

真正执行的时候,通过找到实例对象指向的类型对象的函数指针来执行方法。这里同一个函数在不同情况下表现出了不同的行为,这正是多态的核心所在。

四、引用计数

在 Python 中,主要是通过Py_INCREF(op)Py_DECREF(op)两个宏来增加和减少一个对象的引用计数。当一个对象的引用计数减少到 0 之后, Py_DECREF将调用该对象的析构函数(deallocator function)(但是不一定真的释放该对象所占有的内存和系统资源),即类型对象中tp_dealloc指向的函数。例外的是类型对象,PyTypeObject是超越引用计数规则的,永远不会被析构,每一个对象中指向类型对象的指针不被视为对类型对象的引用。

这有些观察者模式(Observer)的影子,在ob_refcnt减为 0 之后,将触发对象销毁的事件;从 Python 的对象体系来看,各个对象又提供了不同的事件处理函数,而事件的注册动作正是在各个对象对应的类型对象中静态完成的。

PyObject中我们看到ob_refcnt是一个 32 位的整形变量,这实际是一个Python所做的假设,即对一个对象的引用不会超过一个整形变量的最大值。

五、Python对象的分类

python源码剖析之PyObject详解

到此这篇关于python源码剖析之PyObject详解的文章就介绍到这了,更多相关python源码PyObject内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
使用Python进行稳定可靠的文件操作详解
Dec 31 Python
浅析Python基础-流程控制
Mar 18 Python
python daemon守护进程实现
Aug 27 Python
Python合并字典键值并去除重复元素的实例
Dec 18 Python
Python实现 版本号对比功能的实例代码
Apr 18 Python
django如何自己创建一个中间件
Jul 24 Python
Python字符串处理的8招秘籍(小结)
Aug 13 Python
利用pandas合并多个excel的方法示例
Oct 10 Python
numpy中三维数组中加入元素后的位置详解
Nov 28 Python
使用OpenCV校准鱼眼镜头的方法
Nov 26 Python
Python3利用scapy局域网实现自动多线程arp扫描功能
Jan 21 Python
Python实现简单得递归下降Parser
May 02 Python
Python3 如何开启自带http服务
May 18 #Python
安装pytorch时报sslerror错误的解决方案
Pytorch 如何实现LSTM时间序列预测
pytorch实现ResNet结构的实例代码
pytorch常用数据类型所占字节数对照表一览
May 17 #Python
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
Python编写可视化界面的全过程(Python+PyCharm+PyQt)
You might like
浅谈PHP eval()函数定义和用法
2016/06/21 PHP
php和html的区别点详细总结
2019/09/24 PHP
PHP isset()及empty()用法区别详解
2020/08/29 PHP
Javascript条件判断使用小技巧总结
2008/09/08 Javascript
JavaScript 直接操作本地文件的实现代码
2009/12/01 Javascript
基于jquery实现的鼠标滑过按钮改变背景图片
2011/07/15 Javascript
ASP.NET中AJAX 调用实例代码
2012/05/03 Javascript
两种常用的javascript数组去重方法思路及代码
2013/03/26 Javascript
js使用栈来实现10进制转8进制与取除数及余数
2014/06/11 Javascript
javascript实现简单的on事件绑定
2016/08/23 Javascript
AngularJS ng-template寄宿方式用法分析
2016/11/07 Javascript
利用angular.copy取消变量的双向绑定与解析
2016/11/25 Javascript
Angular 4.x 动态创建表单实例
2017/04/25 Javascript
vue.js实现插入数值与表达式的方法分析
2018/07/06 Javascript
js中split()方法得到的数组长度问题
2018/07/19 Javascript
vue-swiper的使用教程
2018/08/30 Javascript
vue实现跳转接口push 转场动画示例
2019/11/01 Javascript
[47:45]Liquid vs OG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python下使用Psyco模块优化运行速度
2015/04/05 Python
python下10个简单实例代码
2017/11/15 Python
tensorflow 用矩阵运算替换for循环 用tf.tile而不写for的方法
2018/07/27 Python
django的settings中设置中文支持的实现
2019/04/28 Python
修改 CentOS 6.x 上默认Python的方法
2019/09/06 Python
Keras loss函数剖析
2020/07/06 Python
pycharm 实现调试窗口恢复
2021/02/05 Python
美国购买汽车零件网站:Buy Auto Parts
2018/04/02 全球购物
Europcar德国:全球汽车租赁领域的领导者
2018/08/15 全球购物
演讲稿开场白
2014/01/13 职场文书
小学优秀教育工作者事迹材料
2014/05/09 职场文书
户外宣传策划方案
2014/05/25 职场文书
迎国庆横幅标语
2014/10/08 职场文书
四风查摆剖析材料
2014/10/10 职场文书
工程资料员岗位职责
2015/04/13 职场文书
个人收入证明范本
2015/06/12 职场文书
pytorch常用数据类型所占字节数对照表一览
2021/05/17 Python
浅析Python中的套接字编程
2021/06/22 Python