前言
在python的源代码中,PyObject的结构体定义如下,它的内容看起来很简单,只有3项,分别是:_PyObject_HEAD_EXTRA
,ob_refcnt
和ob_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。
看到这里,你也许心中已经有了一个非常明确的猜想:”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类型来,我们来对这两个对象做一些对比,看看有什么不同。
如上图所示,我们先实例化整数对象,然后将其转成float对象,第一次进行转换时,转换成功;第二次声明整数对象时成功,但是进行转换时就失败了,报错也很明显:就是这个数太大了无法转换成float对象。
此时,我们停止转换,再次继续声明更大的整数对象:
:::info
tips:float对象的最大取值大家可以探索源码去寻找。
:::
可以看到,没有报错,说明声明成功!(因为数字太大,就没有打印),这看起来整数对象的大小没有限制,想申请多大就申请多大,实际上,就是这样的!看它下面的结构定义就知道啦!它的值没有直接存在一个地址中,而是分开存在一个名叫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