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实现在无须过多援引的情况下创建字典的方法
Sep 25 Python
Python求两个list的差集、交集与并集的方法
Nov 01 Python
使用Python读写文本文件及编写简单的文本编辑器
Mar 11 Python
python中Apriori算法实现讲解
Dec 10 Python
Python实现base64编码的图片保存到本地功能示例
Jun 22 Python
用Python将结果保存为xlsx的方法
Jan 28 Python
python pandas模块基础学习详解
Jul 03 Python
windows上安装python3教程以及环境变量配置详解
Jul 18 Python
pytorch numpy list类型之间的相互转换实例
Aug 18 Python
Python基础之字符串操作常用函数集合
Feb 09 Python
mac在matplotlib中显示中文的操作方法
Mar 06 Python
Python Django模型详解
Oct 05 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
收音机玩机评测 406 篇视频合集
2020/03/11 无线电
php中将地址生成迅雷快车旋风链接的代码[测试通过]
2011/04/20 PHP
深入PHP中的HashTable结构详解
2013/06/13 PHP
Yii实现微信公众号场景二维码的方法实例
2020/08/30 PHP
JavaScript 学习笔记(十二) dom
2010/01/21 Javascript
JQuery的html(data)方法与<script>脚本块的解决方法
2010/03/09 Javascript
javascript复制对象使用说明
2011/06/28 Javascript
分享一则javascript 调试技巧
2015/01/02 Javascript
JQuery实现Ajax加载图片的方法
2015/12/24 Javascript
JavaScript中的事件委托及好处
2016/07/12 Javascript
BootStrap使用file-input插件上传图片的方法
2016/09/05 Javascript
jquery radio的取值_radio的选中_radio的重置方法
2016/09/20 Javascript
浅析javascript中的Event事件
2016/12/09 Javascript
微信小程序实战之上拉(分页加载)效果(2)
2017/04/17 Javascript
详解升级react-router 4 踩坑指南
2017/08/14 Javascript
vuejs 制作背景淡入淡出切换动画的实例
2018/09/01 Javascript
electron实现qq快捷登录的方法示例
2018/10/22 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
vue实现分页加载效果
2019/12/24 Javascript
[07:27]DOTA2卡尔工作室 英雄介绍水晶室女篇
2013/06/21 DOTA
Python语言的12个基础知识点小结
2014/07/10 Python
浅谈python之新式类
2018/08/12 Python
Python使用reportlab模块生成PDF格式的文档
2019/03/11 Python
Django中如何使用sass的方法步骤
2019/07/09 Python
你还在@微信官方?聊聊Python生成你想要的微信头像
2019/09/25 Python
什么是python的必选参数
2020/06/21 Python
keras 简单 lstm实例(基于one-hot编码)
2020/07/02 Python
python UDF 实现对csv批量md5加密操作
2021/01/01 Python
如何使用html5与css3完成google涂鸦动画
2012/12/16 HTML / CSS
HTML5和以前HTML4的区别整理
2013/10/20 HTML / CSS
什么是用户模式(User Mode)与内核模式(Kernel Mode) ?
2015/09/07 面试题
在weblogic中发布ejb需涉及到哪些配置文件
2012/01/17 面试题
自我评价怎么写好呢?
2013/12/05 职场文书
网络管理员岗位职责
2014/03/17 职场文书
2014年体育部工作总结
2014/11/13 职场文书
OpenCV 图像梯度的实现方法
2021/07/25 Python