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使用多线程不断刷新网页的方法
Mar 31 Python
初步理解Python进程的信号通讯
Apr 09 Python
使用Python的web.py框架实现类似Django的ORM查询的教程
May 02 Python
Python的标准模块包json详解
Mar 13 Python
Python 稀疏矩阵-sparse 存储和转换
May 27 Python
Python爬虫包BeautifulSoup学习实例(五)
Jun 17 Python
朴素贝叶斯Python实例及解析
Nov 19 Python
python实现根据文件关键字进行切分为多个文件的示例
Dec 10 Python
关于python下cv.waitKey无响应的原因及解决方法
Jan 10 Python
python basemap 画出经纬度并标定的实例
Jul 09 Python
Python算法中的时间复杂度问题
Nov 19 Python
python3.7 openpyxl 在excel单元格中写入数据实例
Sep 01 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
javascript some()函数用法详解
2014/11/13 PHP
通过php修改xml文档内容的方法
2015/01/23 PHP
浅析PHP中Session可能会引起并发问题
2015/07/23 PHP
在Mac上编译安装PHP7的开发环境
2015/07/28 PHP
PHP实现长文章分页实例代码(附源码)
2016/02/03 PHP
php将一维数组转换为每3个连续值组成的二维数组
2016/05/06 PHP
Yii框架实现图片上传的方法详解
2017/05/20 PHP
Laravel 模型使用软删除-左连接查询-表起别名示例
2019/10/24 PHP
用js计算页面执行时间的函数
2006/12/07 Javascript
JQuery 操作Javascript对象和数组的工具函数小结
2010/01/22 Javascript
基于Jquery的简单&简陋Tabs插件代码
2010/02/09 Javascript
深入理解Javascript闭包 新手版
2010/12/28 Javascript
js鼠标滑过弹出层的定位IE6bug解决办法
2012/12/26 Javascript
探讨js字符串数组拼接的性能问题
2014/10/11 Javascript
jQuery实现折线图的方法
2015/02/28 Javascript
JQuery菜单效果的两个实例讲解(3)
2015/09/17 Javascript
AngularJS内建服务$location及其功能详解
2016/07/01 Javascript
详解Jquery Easyui的验证扩展
2017/01/09 Javascript
JavaScript中的遍历详解(多种遍历)
2017/04/07 Javascript
node中的cookie的具体使用
2018/09/13 Javascript
浅谈Python 的枚举 Enum
2017/06/12 Python
python生成器,可迭代对象,迭代器区别和联系
2018/02/04 Python
Python单元和文档测试实例详解
2019/04/11 Python
详解Python二维数组与三维数组切片的方法
2019/07/18 Python
python批量修改文件名的示例
2020/09/27 Python
详解使用canvas保存网页为pdf文件支持跨域
2018/11/23 HTML / CSS
纬创Java面试题笔试题
2014/10/02 面试题
工程安全员岗位职责
2014/03/09 职场文书
工业自动化专业自荐信范文
2014/04/10 职场文书
爱耳日活动总结
2014/04/30 职场文书
本科生就业推荐信
2014/05/19 职场文书
建筑施工安全生产责任书
2014/07/22 职场文书
食品质量与安全专业毕业生求职信
2014/08/11 职场文书
结婚保证书(卖身契)
2015/02/26 职场文书
大学入学感言
2015/08/01 职场文书
python APScheduler执行定时任务介绍
2022/04/19 Python