python源码剖析之PyObject详解
作者:莫彩 发布时间:2023-08-02 14:07:03
一、Python中的对象
Python中一切皆是对象。
————Guido van Rossum(1989)
这句话只要你学过python,你就很有可能在你的Python学习之旅的前30分钟就已经见过了,但是这句话具体是什么意思呢?
一句话来说,就是面向对象中的“类”和“对象”在Python中都是对象。类似于int对象的类型对象,实现了“类的概念”,对类型对象“实例化”得到的实例对象实现了“对象”这个概念。
通常的说法是,对象是数据以及基于这些数据的操作的集合。在计算机上,一个对象实际上就是一片被分配的内存空间,这些内存可能是连续的,也有可能是离散的,这都不重要,重要的是这片内存在更高的层次上可以作为一个整体来考虑,这个整体就是一个对象。在这片内存中,存储着一系列的数据以及可以对这些数据进行修改或读取的一系列操作的代码。
在 Python 中,对象就是在堆上申请的结构体,对象不能是被静态初始化的,并且也不能是在栈空间上生存的。唯一的例外就是类型对象(type object),Python中所有的类型对象都是被静态初始化的。在 Python 中,一个对象一旦被创建,它在内存中的大小就是不变的了。 这就意味着那些需要容纳可变长度数据的对象只能在对象内维护一个指向一个可变大小的内存区域的指针。
1.1 对象机制的基石PyObject
PyObject
和 PyVarObject
分别表示定长对象和变长对象,使用的C的struct
实现的,在结构中分别只定义了 PyObject_HEAD
和 PyObject_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中对象共有的部分,其他的具体类型会有额外的结构体来定义,否则的话所有的对象岂不是都一样了?比如int类型的结构体定义PyIntObject
中包含了PyObject_HEAD
和ob_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
函数指针,如果是自定义对象,这个指针可能是空,那就通过PyTypeObject
的tp_base
找到类型对象的基类,再找tp_new
指针,这个过程中会利用类型对象中记录的空间信息申请内存。对于 Python 中的任何一种变长对象,tp_itemsize
这个域是必须设置的,tp_itemsize
指明了由变长对象保存的元素的单位长度,所谓单位长度即是指一个对象在内存中的长度。这个 tp_itemsize
和ob_size
共同决定了应该额外申请的内存的总大小是多少。
内建对象最终会使用COL完成创建工作。
new
函数完成后,会执行init
函数,前者类似于new操作符,后者类似于构造函数。
2.2 对象的行为
像前面说的,对象的行为被类型对象中的函数指针所定义。这些操作中,有三组非常重要的操作族:tp_as_number
、tp_as_sequence
、tp_as_mapping
分别指向PyNumberMethods
、PtSequenceMethods
、PyMappingMethods
函数族结构体。所谓“鸭子类型”,就行能找到该类型对应的操作,就可以当做这个类型来用。
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对象,那么类型对象PyObject
的ob_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 利用 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对象的分类
来源:https://blog.csdn.net/cookieZZ/article/details/116763833
猜你喜欢
- 前段时间在网上找了一个“完美的”JavaScript对象克隆的函数,感觉还不错,但随后便出现了一些问题,发现这个克隆并不好用,在使用发现了如
- 本文主要概括安装时提示有挂起的操作、收缩数据库、压缩数据库、转移数据库给新用户以已存在用户权限、检查备份集、修复数据库等操作技巧。1.挂起操
- 每个程序员都绝对必须知道的关于字符集和Unicode的那点儿事(别找借口!)Unicode与字符集你曾经是否觉得HTML中的"Co
- 我们现在使用的验证手段都是以验证码为主,让用户根据图片输入验证字符,这种方法的安全度尚可,但会给用户带来一些不便和困扰,比如这个雅虎的验证码
- 人类学是关于人的研究;社会人类学(social anthropology)是研究人类社会的学科。社会人类学还可以理解成“文化翻译”(the
- 【先锋缓存类】Ver2004作者:孙立宇、apollosun、ezhonghua官方网站:http://www.lkstar.com 技术支
- redux-saga在学习它之前先了解es6生成器生成器关键字:yield next()定义函数需要在函数名前急+*号function *t
- Web网站可用性的关键指标是速度,更确切地说,是页面能以多快的速度出现在访问者的浏览器窗口里。影响速度的因素有很多种,包括Web服务器的速度
- 作者: Alan Pearce原文: Multi-Column Layouts Climb Out of the Box地址: http:/
- c shell perl php下的日期时间转换: 秒数与人类可读日期 scalar localtime 与 seconds since `
- 终于完成了偶的拖动窗口,花了近15个小时,庆祝一下(*^__^*);以前写了IE下的功能,于是又写了firefox下的功能,在firefox
- oblog 推出了4.0的最新版本,这个不是重点,重点是4.0的版本中附带了xml-rpc支持。oblog的支持代表着大量的国内blog站点
- MacJi “偷懒”翻译了部分,下午冒着被 BOSS 开除的危险将其补完(原文链接)。使用 line-height 垂直居中line-hei
- 下面就来介绍下SQL Server 2008中使用的端口有哪些:首先,最常用最常见的就是1433端口。这个是数据库引擎的端口,如果我们要远程
- 初学ASP,程序是能勉强写出来了,但若每进行一次网站页面的改版,所有的源程序都将进行一次移植手术。为此所耗费的人力精力不计其数,甚至一不小心
- 而标准的事件触发可以使用dispatchEvent方法。但现在FF5无法触发了A的默认行为了。如下 <!doctype html>
- 如下所示:a[:, np.newaxis] # 给a最外层中括号中的每一个元素加[]a[np.newaxis, :] # 给a最外层中括号中
- PHP Too few arguments to function的解决过去自定义函数的时候如果参数不足,则会抛出一个警告,但是在7.1开始
- 前言本文的操作环境:ubuntu,Python2.7,采用的是Pycharm进行代码编辑,个人很喜欢它的代码自动补齐功能。示例图如上图,我们
- 导航标签彼此互斥、完全穷尽。导航标签其实就是一种文字表达形式,我们用标签来代表网站上的各种分类信息。比如“联系我们”这个标签,代表的内容通