《Python源码剖析》之对象的基石---PyObject

前言

在python的源代码中,PyObject的结构体定义如下,它的内容看起来很简单,只有3项,分别是:_PyObject_HEAD_EXTRAob_refcntob_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_EXTRA
    Py_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。
image.243eca7cef4411eebf312b20f7a91591.png

看到这里,你也许心中已经有了一个非常明确的猜想:”object是任何类型的父类,或者说,python中的任何一个类都继承自object类,除了object自己外“!我相信你一定很确信这一点,但是为什么是这样子呢?为什么它的表现或者行为是这样子呢?在揭开这层薄薄的神秘的面纱之前,你始终无法确认你这个猜想为事实,如果你是一个求真务实的程序员的话。🙃

揭开面纱

当你开始探索这些对象的源码时,你将不需花费吹灰之力揭开这层薄薄的面纱!首先,我们来探索一下一个简单的对象—float,它的源码实现如下:

// floatobject.h
typedef struct {
    PyObject_HEAD
    double 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_HEAD
    digit 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类型来,我们来对这两个对象做一些对比,看看有什么不同。
image.f91adf6afa4d11eebf312b20f7a91591.png
如上图所示,我们先实例化整数对象,然后将其转成float对象,第一次进行转换时,转换成功;第二次声明整数对象时成功,但是进行转换时就失败了,报错也很明显:就是这个数太大了无法转换成float对象。
此时,我们停止转换,再次继续声明更大的整数对象:

:::info
tips:float对象的最大取值大家可以探索源码去寻找。

:::
image.49e86060fa4f11eebf312b20f7a91591.png
可以看到,没有报错,说明声明成功!(因为数字太大,就没有打印),这看起来整数对象的大小没有限制,想申请多大就申请多大,实际上,就是这样的!看它下面的结构定义就知道啦!它的值没有直接存在一个地址中,而是分开存在一个名叫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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值