Python对象的底层实现源码学习

目录
  • 1. PyObject:对象的基石
  • 2. PyVarObject:变长对象的基础
    • 2.1 浮点对象
    • 2.2 列表对象
  • 3. PyTypeObject:类型的基石
  • 4. PyType_Type:类型的类型
  • 5. PyBaseObject_Type:类型之基
  • 6. 补充

在“Python源码学习笔记:Python万物皆对象”中,我们对Python的对象类型体系有了一定的认识,这篇博客将从源码层面来介绍Python中万物皆对象的底层实现。

1. PyObject:对象的基石

在Python解释器的C层面,一切对象都是以PyObject为基础的

C源码如下:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

源码解读:

_PyObject_HEAD_EXTRA:主要用于实现双向链表(分析源码时暂时忽略)

ob_refcnt:引用计数,用于垃圾回收机制,当这个参数减少为0时即代表对象要被删除了(Py_ssize_t当作int或long即可,感兴趣的话可以去看下它的定义)

ob_type:类型指针,指向对象的类型对象(PyTypeObject,稍后介绍),类型对象描述实例对象的数据及行为。如PyLongObject的ob_type指向的就是PyLong_Type

2. PyVarObject:变长对象的基础

PyVarObject与PyObject相比只多了一个属性ob_size,它指明了边长对象中有多少个元素

C源码如下:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

定长对象和变长对象的大致结构图示如下:

宏定义:对于具体对象,视其大小是否固定,需要包含头部PyObject或PyVarObject,为此,头文件准备了两个宏定义,方便其他对象使用:

#define PyObject_HEAD       PyObject ob_base;
#define PyObject_VAR_HEAD   PyVarObject ob_base;

2.1 浮点对象

这里简单的以浮点对象作为定长对象的例子,介绍一下相关概念,后续会详细分析float对象的源码。

对于大小固定的浮点对象,需要在PyObject头部的基础上,用一个双精度浮点数double加以实现:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

图示如下:

2.2 列表对象

这里简单的以列表对象作为变长对象的例子,介绍一下相关概念,后续会详细分析list对象的源码。

对于大小不固定的列表对象,需要在PyVarObject头部的基础上,用一个动态数组加以实现,数组存储了列表包含的对象的指针,即PyObject指针:

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

源码解读:

ob_item:指向动态数组的指针,数组中保存元素对象指针

allocated:动态数组的总长度,即列表当前的“容量”

ob_size:当前元素个数,即列表当前的长度(这里的长度是指:列表包含n个元素,则长度为n)

图示如下:

3. PyTypeObject:类型的基石

问题:不同类型的对象所需存储空间不同,创建对象时从哪得知存储信息呢?以及如何判断一个给定对象支持哪些操作呢?

注意到,PyObject结构体中包含一个指针ob_type,指向的就是类型对象,其中就包含了上述问题所需要的信息

C源码如下:(只列出了部分,后续会结合具体类型进行分析)

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
    /* Methods to implement standard operations */
    destructor tp_dealloc;
    printfunc tp_print
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    // ...
    /* Attribute descriptor and subclassing stuff */
    PyObject *tp_bases;
	// ...
} PyTypeObject;

源码解读:

PyObject_VAR_HEAD表示PyTypeObject是变长对象

tp_name:类型名称

tp_basicsize、tp_itemsize:创建实例对象时所需的内存信息

tp_print、tp_getattr等:表示该类型支持的相关操作信息

tp_bases:指向基类对象,表示类型的继承信息

PyTypeObject就是类型对象在C层面的表示形式,对应面向对象中”类“的概念,其中保存着对象的”元信息“(即一类对象的操作、数据等)。

下面以浮点类型为例,列出了PyFloatObject和PyTypeObject之间的关系结构图示:(其中两个浮点实例对象都是PyFloatObject结构体,浮点类型对象float是一个PyTypeObject结构体变量)

由于浮点类型对象唯一,在C语言层面作为一个全局变量静态定义即可。C源码如下:(只列出了部分)

PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),
    0,
    (destructor)float_dealloc,                  /* tp_dealloc */
    // ...
    (reprfunc)float_repr,                       /* tp_repr */
    // ...
};

源码解读:

第二行PyVarObject_HEAD_INIT(&PyType_Type, 0):初始化了ob_refcnt、ob_type、ob_sie三个字段,其中ob_type指向了PyType_Type(稍后会继续介绍,它就是type),即:float的类型是type

第三行"float":将tp_name字段初始化为类型名称float

4. PyType_Type:类型的类型

通过PyFloat_Type的ob_type字段,我们找到了type所对应的C语言层面结构体变量:PyType_Type,C源码如下:(只列出了部分)

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */
    (destructor)type_dealloc,                   /* tp_dealloc */
    // ...
    (reprfunc)type_repr,                        /* tp_repr */
    // ...
};

内建类型和自定义类对应的PyTypeObject对象都是通过这个PyType_Type创建的。在第二行PyVarObject_HEAD_INIT(&PyType_Type, 0)中,PyType_Type把自己的ob_type字段设置成了它自己,即type的类型是type

把PyType_Type加入到结构图中,图示如下:

5. PyBaseObject_Type:类型之基

object是另外一个特殊的类型,它是所有类型的基类。如果要找到object对应的结构体,我们可以通过PyFloat_Type的tp_base字段来寻找,因为它指向的就是float的基类object。但是我们查看源码发现,PyFloat_Type中并没有初始化tp_base字段:

同样地,我们查看Objects文件夹下的各种不同类型所对应的结构体,发现tp_base字段均没有初始化,于是寻找将tp_base字段初始化的函数:

void
_Py_ReadyTypes(void)
{
    if (PyType_Ready(&PyBaseObject_Type) < 0)
        Py_FatalError("Can't initialize object type");
    if (PyType_Ready(&PyType_Type) < 0)
        Py_FatalError("Can't initialize type type");
    // ...
    if (PyType_Ready(&PyFloat_Type) < 0)
        Py_FatalError("Can't initialize float type");
    // ...
}

_Py_ReadyTypes中统一调用了PyType_Ready()函数,为各种类型设置tp_base字段:

int
PyType_Ready(PyTypeObject *type)
{
    // ...
    /* Initialize tp_base (defaults to BaseObject unless that's us) */
    base = type->tp_base;
    if (base == NULL && type != &PyBaseObject_Type) {
        base = type->tp_base = &PyBaseObject_Type;
        Py_INCREF(base);
    }
    // ...
}

可以看到,PyType_Ready在初始化tp_base字段时,对于PyBaseObject_Type,不会设置tp_base字段,即object是没有基类的,这就是为了保证继承链有一个终点。

PyBaseObject_Type源码如下:(只列出了部分)

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "object",                                   /* tp_name */
    sizeof(PyObject),                           /* tp_basicsize */
    0,                                          /* tp_itemsize */
    object_dealloc,                             /* tp_dealloc */
    // ...
    object_repr,                                /* tp_repr */
    // ...
    0,                                          /* tp_base */
    // ...
};

源码解读:

第二行PyVarObject_HEAD_INIT(&PyType_Type, 0):把ob_type设置为PyType_Type,即object的类型是type

将PyBaseObject_Type加入到结构图中,图示如下:

6. 补充

object的类型是type,type的基类是object。先有鸡还是先有蛋?

答:

前面我们提到,在各种类型对应的C语言结构体变量初始化的时候,tp_base字段都是没有设置具体值的,直到_Py_ReadyTypes()函数执行时,才通过PyType_Ready()去初始化各类型的tp_base。

在PyBaseObject_Type初始化时,会将ob_tyep字段设置为PyType_Type,即object的类型为type;在_Py_ReadyTypes函数中,会通过PyType_Ready()设置PyType_Type的tp_base字段为PyBaseObject_Type。所以这里本质上不是一个先有鸡还是先有蛋的问题。

PyTypeObject保存元信息:某种类型的实例对象所共有的信息保存在类型对象中,实例对象所特有的信息保存在实例对象中。以float为例:

  • 无论是3.14,还是2.71,作为float对象,它们都支持加法运算,因此加法处理函数的指针就会保存在类型对象中,即float中。
  • 而这两个float对象的具体值都是各自特有的,因此具体数值会通过一个double类型的字段保存在实例对象中。

以上就是Python对象的底层实现源码学习的详细内容,更多关于Python对象底层的资料请关注我们其它相关文章!

(0)

相关推荐

  • python基础中的文件对象详解

    目录 一.python读取和写入文件内容 二.文件对象的写入 三.实现文件内容的拷贝 四.通过文件对象cmd.exe对命令行工具进行复制 总结 一.python读取和写入文件内容 任务:在cmd默认登陆目录中建立一个命名为test.txt的文件并写入内容“welcome python” 打开文件的三个步骤 1.建立文件对象-打开冰箱门 2.读取文件-把大象拿出来 f = open("C:\\Users\\Administrator\\test.txt","rb")#

  • python基础之定义类和对象详解

    定义简单的类 面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了! 定义只包含方法的类 在python中,要定义一个只包含方法的类,语法格式如下: #用class定义类名 class 类名: def 方法1(self,参数列表) #方法的属性中有且仅有一个self pass def 方法2(self,参数列表) #类之外定义的def是函数,在类里面定义的def叫做方法,而且括号里必须要有self pass class Student: pass

  • Python万物皆对象理解及源码学习

    目录 万物皆对象 1 类型对象和实例对象 2 类型.对象体系 2.1 元类型type 2.2 自定义类型 2.3 自定义类型子类 2.4 type和object的关系 3 可变对象与不可变对象 4 变长对象和定长对象 5 补充 万物皆对象 这篇博客的内容主要是针对Python中万物皆对象的理解,对Python的类型.对象体系做一个整体的梳理. 在Python中,一切皆为对象,一个整数是一个对象,一个字符串也是一个对象,基本类型(如int)也是对象.Python不再区别对待基本类型和对象,所有的基

  • 关于python的对象序列化介绍

    对象:是抽象的概念 如列表 元组 字典 集合 皆为对象 序列化:一种方法.目的:把对象存储在磁盘上(即,将对象转换为字节数据/字符数据). 这一过程我们借助序列化这一方法实现. 为什么需要序列化和反序列化这一操作呢? 1.便于存储.序列化过程将文本信息转变为二进制数据流.信息就容易存储在硬盘之中,当需要读取文件的时候,从硬盘中读取数据,然后再将其反序列化便可以得到原始的数据.在Python程序运行中得到了一些字符串.列表.字典等数据,想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断

  • Python学习之面向对象编程详解

    目录 什么是面向对象编程(类) 类的关键字-class 类的定义与使用 类的参数-self self的解析与总结 类的构造函数 构造函数的创建方法 关于对象的生命周期 什么是面向对象编程(类) 利用(面向)对象的(属性和方法)去进行编码的过程即面向对象编程 自定义对象数据类型就是面向对象中的类(class)的概念 类的关键字 - class class 关键字用来声明类,类的名称首字母大写,多单词的情况下每个单词首字母大写(即驼峰命名法).在我们一开始学习 Python 的时候说过,要尽量避免使

  • Python对象的生命周期源码学习

    目录 思考: 1 C API 2 对象的创建 2.1 两种创建对象的方式 2.2 由类型对象创建实例对象 3 对象的多态性 4 对象的行为 5 引用计数 思考: 当我们输入这个语句的时候,Python内部是如何去创建这个对象的? a = 1.0 对象使用完毕,销毁的时机又是怎么确定的呢? 下面,我们以一个基本类型float为例,来分析对象从创建到销毁这整个生命周期中的行为. 1 C API Python是用C写的,对外提供了API,让用户可以从C环境中与其交互,并且Python内部也大量使用了这

  • Python对象的底层实现源码学习

    目录 1. PyObject:对象的基石 2. PyVarObject:变长对象的基础 2.1 浮点对象 2.2 列表对象 3. PyTypeObject:类型的基石 4. PyType_Type:类型的类型 5. PyBaseObject_Type:类型之基 6. 补充 在“Python源码学习笔记:Python万物皆对象”中,我们对Python的对象类型体系有了一定的认识,这篇博客将从源码层面来介绍Python中万物皆对象的底层实现. 1. PyObject:对象的基石 在Python解释器

  • Python内建类型int源码学习

    目录 1 int对象的设计 1.1 PyLongObject 1.2 整数的布局 1.3 小整数静态对象池 1.4 示例 2 大整数运算 2.1 整数运算概述 2.2 大整数运算处理过程 1.long_add()源码: 2.绝对值加法x_add() 3 其他 大整数转float溢出 “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种常用的内建类型. 问题:对于C语言,下面这个程序运行后的结果是什么?是1000000000000吗? #include <stdio

  • Python内建类型str源码学习

    目录 引言 1 Unicode 2 Python中的Unicode 2.1 Unicode对象的好处 2.2 Python对Unicode的优化 3 Unicode对象的底层结构体 3.1 PyASCIIObject 3.2 PyCompactUnicodeObject 3.3 PyUnicodeObject 3.4 示例 4 interned机制 5 总结 引言 “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种常用的内建类型. 在介绍常用类型str之前,在上

  • Python内建类型list源码学习

    目录 问题: 1 常用方法 小结: 题外话: 2 list的内部结构:PyListObject 3 尾部操作和头部操作 3.1 尾部操作 3.2 头部操作 4 浅拷贝和深拷贝 4.1 浅拷贝 4.2 深拷贝 4.3 直接赋值 4.4 小结 个人总结: TODO: 5 动态数组 5.1 容量调整 5.2 append() 5.3 insert() 5.4 pop() 5.5 remove() 6 一些问题 问题: “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种

  • Python内建类型float源码学习

    目录 1 回顾float的基础知识 1.1 PyFloatObject 1.2 PyFloat_Type 1.3 对象的创建 1.4 对象的销毁 1.5 小结 2 空闲对象缓存池 2.1 浮点对象的空闲链表 2.2 空闲链表的使用 3 其他 “深入认识Python内建类型”这部分的内容会从源码角度为大家介绍Python中各种常用的内建类型. 1 回顾float的基础知识 1.1 PyFloatObject 1.2 PyFloat_Type C源码(仅列出部分字段): PyTypeObject P

  • Python作用域与名字空间源码学习笔记

    目录 作用域与名字空间 1. 名字绑定 1.1 赋值 1.2 模块导入 1.3 函数.类定义 1.4 as关键字 2. 作用域 2.1 静态作用域 2.2 划分作用域 2.3 闭包作用域 2.4 类作用域 2.5 复杂嵌套 2.5.1 函数嵌套类 2.5.2 类嵌套类 3. 名字空间 3.1 Globals 3.2 Locals 3.3 Enclosings 3.4 Builtin 4. 问题与总结 作用域与名字空间 问题: PI = 3.14 def circle_area(r): retur

  • Python中for循环可迭代对象迭代器及生成器源码学习

    目录 问题: 1. 迭代 1.1 可迭代对象Iterable 1.2 迭代器Iterator 1.3 for循环 1.3.1 iter()方法和next()方法 1.3.2 iter()和__iter__() 1.3.3 next()和__next__() 1.3.4 自定义类实现__iter__()和__next__() 1.3.5 探究for循环 2 生成器 2.1 获取生成器 2.2 next(生成器) 2.3 生成器和迭代器 2.4 生成器解析式 问题: 之前在学习list和dict相关

  • Python虚拟机栈帧对象及获取源码学习

    目录 Python虚拟机 1. 栈帧对象 1.1 PyFrameObject 1.2 栈帧对象链 1.3 栈帧获取 2. 字节码执行 Python虚拟机 注:本篇是根据教程学习记录的笔记,部分内容与教程是相同的,因为转载需要填链接,但是没有,所以填的原创,如果侵权会直接删除.此外,本篇内容大部分都咨询了ChatGPT,为笔者解决了很多问题. 问题: 在Python 程序执行过程与字节码中,我们研究了Python程序的编译过程:通过Python解释器中的编译器对 Python 源码进行编译,最终获

随机推荐