Python源码剖析-深度探索动态语言核心技术

Python源码剖析-深度探索动态语言核心技术

  • 内容简介
  • Python 总体架构
  • Python源代码的组织
  • 第一部分:Python内建对象
    • 第1章:Python对象初探
    • 1.1 Python内的对象
      • 1.1.1 对象的基石-----PyObject
      • 1.1.2 定长对象
      • 重要点总结
    • 1.2 类型对象
      • 1.2.1 对象的创建
      • 1.2.2 对象的行为
      • 1.2.3 类型的类型
    • 1.3 Python对象的多态性
    • 1.4 引用计数
    • 1.5 Python 对象的分类
    • 第2章:Python中的整数对象
    • 2.1 初识 PyIntObject 对象
    • 2.2 PyIntObject 对象的创建和维护
      • 2.2.1 对象创建的3种途径

内容简介

为了更好地利用python语言,无论是python本身还是和C/C++配合使用,深刻理解python的运行原理都是非常重要的,本文以CPython源码为研究对象,深入剖析Python的实现。主要包含:

  • Python内建对象
  • Python虚拟机
  • Python高级特性

本文参考: Python源码剖析一书

学习建议:唯有亲身尝试,才能深解其中三昧。 在真实的源码和文章的描述相比较揣摩,动手捣鼓捣鼓Python的源码,用真实的输出来验证文章描述和自己的理解。

Python 总体架构

首先我们应该了解python的整体架构,对python 有个宏观的认识。

Python整体架构分为三部分,如下图:

  • 左边是python提供的大量模块,库,以及用户自定义模块
  • 右边是python的运行时环境,包括对象/类型系统, 内存分配器, 运行时状态信息。
  • 中间部分为解释器(虚拟机), 包括词法分析,语法分析,编译器与 (虚拟机Code Evauator)
    Python源码剖析-深度探索动态语言核心技术_第1张图片

Python源代码的组织

获取源码网站: 源码网站

Python源码剖析-深度探索动态语言核心技术_第2张图片

解压后我们将看到目录结构:

  • Includes : 该目录包含了python提供的所有头文件,我们如果用c、c++编写自定义模块扩展python,就需要用到这里提供的头文件
  • Lib:包含了python自带的所有标准库, lib 中的库都是用python编写而成
  • Modules: 该模块包含了所有用C语言编写的模块, 比如random,cstringIO等,其中模块都是那些对速度要求非常严格的模块, (对速度没有严格要求的放在了lib中)
  • Parser: 包含了python解释器中的词法分析Scanner和语法分析 Parser部分
  • Objects: 改目录中包含了所有Python的内建对象,包括整数,list, dict等, 同时包含了Python在运行时需要的所有的内部使用对象的实现。
  • Python:是Python解释器的 Compiler 和执行引擎部分,是其运行的核心所在。

Python源码剖析-深度探索动态语言核心技术_第3张图片

第一部分:Python内建对象

第1章:Python对象初探

我们熟知在面向对象理论中的 “类” 和 “对象” 这两个概念在python中都是使用Python内对象来实现的,也可以理解为一切都是对象, 于是我们将会带着一些疑问进入本章~

  1. 对象在python内部是如何表示的? (在C的层面,是个什么模样)
  2. C不是一个面向对象的语言,那么python的对象机制又是如何实现的呢?

额外一些有意思的相关知识:

  • 在Python中,对象是C中结构体在堆上申请的一块内存,因此对象不能被静态初始化,也不能在栈上生存,例外是(Python的类型对象type,都是被静态初始化的 ~)
  • 在Python中,一个对象一旦被创建,其内存大小就是不变的了,于是python在对象内维护一个指向一块可变大小的内存区域的指针来容纳可变长度数据的对象,当然这也是因为遵循这样的规则是的通过指针维护对象的工作变得简单。(struct _typeobject *ob_type)

1.1 Python内的对象

1.1.1 对象的基石-----PyObject

所有的对象都拥有一些相同的内容, 这些内容就在PyObject中定义, PyObject是整个Python对象体系的核心。

Python源码剖析-深度探索动态语言核心技术_第4张图片

而PyObject的秘密就在这个PyObject_HEAD中~
Python源码剖析-深度探索动态语言核心技术_第5张图片
我们发现,其实在PyObject的定义中, 仅有两个东西:

  • int (py_ssize_t) 类型的 ob_refcnt , 对象引用计数(垃圾回收机制)
  • struct _typeobject *ob_type, 一个指针,来维护一个类型信息结构(后续会再次分析)

PyObject中定义了每一个Python对象都必须有的内容, 每一个Python对象除了拥有PyObject外,还会占据一些额外的内存,保存属于自己的特殊信息,比如 intobject就会多出一个 ob_ival 来表示其值。

Python源码剖析-深度探索动态语言核心技术_第6张图片

1.1.2 定长对象

对于数组,这种 n 个 元素构成的东西也是一类Python对象的共同特征, 因此, 在PyObject对象之外
还有一个表示这类对象的结构体–PyVarObject

Python源码剖析-深度探索动态语言核心技术_第7张图片
Python源码剖析-深度探索动态语言核心技术_第8张图片

变长对象往往都是容器, ob_size 指明了所容纳元素的个数

重要点总结

从PyObject_VAR_HEAD的定义中我们发现, PyVarObject实际上只是对PyObject的扩展而已,其开始部分的字节的意义和 PyObject相同, 一个引用计数加上一个类型信息结构指针,这意味着在Python中对对象的引用变得十分的统一, 使用 PyObject* 指针能引用任意的一个对象。

                  不同的Python对象在内存布局上的关系

Python源码剖析-深度探索动态语言核心技术_第9张图片

1.2 类型对象

在上文我们看到了Python中所有对象的共有信息的定义,但我们会思考,我们所创建的对象的真实描述信息在哪呢? 例如:Int 和 Str 对象占有的空间信息在哪~, 其实这些信息都被隐含在 PyObject 的 _typeobjct 指针指向的地方。

在_typeobjct 的定义中包含了很多的信息,主要分为4类:

  • 类型名
  • 创建该类型时分配的内存空间大小信息 (tp_basicsize 和 tp_itemsize)
  • 与该类型对象相关联的操作信息(诸如 tp_print这样的许多的函数指针)
  • 类型的类型信息

Python源码剖析-深度探索动态语言核心技术_第10张图片

事实上, 一个 PyTypeObject对象就是 Python中对面向对象理论中“类”这个概念的实现,由于PyTypeObject是一个篇幅较大的话题,我们在第二部分来介绍构建在PyTypeObject之上的Python的类型和对象体系。

1.2.1 对象的创建

思考: Python内部究竟如何才能从无到有创建一个整数对象呢?

  1. 通过 Python C API 来创建
  2. 通过类型对象 来创建

Python对外提供了C API,分为两种:

  • AOL,(Abstract Object Layer),形式如 PyObject_***形式, 可以应用在任何Python对象上
PyObject * intObj = PyObject_New(PyObject, &PyInt_Type)
  • COL (Concrete Object Layer) 只能作用于某一种类型的对象上,对于内建对象都有一套API
PyObject *intObj = PyInt_FromLong(10)

对于自定义的类型,比如 Class A(object) 定义的类型A,要创建其对象,由于Python不可能事先提供
PyA_New这样的API, 他将会通过A所对应的类型对象 来创建实例对象。

下面我们举例创建整数对象的函数调用流程(如图):

  • PyInt_Type 中的 tp_new 会被调用,如果tp_new 是空,会到基类找tp_new
  • tp_new会访问PyInt_Type中记录的 tp_basicsize 信息,完成申请内存的操作
  • 之后调用 tp_init ,完成初始化的操作

tp_new, tp_init 对应 new操作符和类的构造函数
Python源码剖析-深度探索动态语言核心技术_第11张图片

1.2.2 对象的行为

对象的行为是通过内置大量函数指针来实现的,这些函数指针就表现了类型对象所定义的操作(行为)。

同时,在PyTypeObject有三个重要的操作族需要介绍一下:

tp_as_number, tp_as_sequence, tp_as_mapping,
分别指向 PyNumberMethods, PySequenceMethods 和 PyMappingMethods 函数族。

Python源码剖析-深度探索动态语言核心技术_第12张图片

这里我们以序列操作族为例子:
也可以说当 tp_as_sequence 指针不为空时(list,str),表现出的行为就是序列行为。
Python源码剖析-深度探索动态语言核心技术_第13张图片

1.2.3 类型的类型

在我们的PyTypeObject定义的最开始也会存在 PyObject_Var_HEAD, 证明Python中的类型本身也是一个对象,其类型是 PyType_Type:
Python源码剖析-深度探索动态语言核心技术_第14张图片

  • PyType_Type 在 Python的类型机制是非常关键的,所有用户自定义Class所对应的 PyTypeObject对象都是通过这个对象创建的。

这里我们来看一下Int类型的定义,它会调用 PyObject_HEAD_INIT这个宏来初始化公共的头部分:
本质上就是将 ob_type 指向 PyType_Type, 并将其引用计数设置为1
Python源码剖析-深度探索动态语言核心技术_第15张图片

Python源码剖析-深度探索动态语言核心技术_第16张图片

现在我们可以想象一个整数对象在运行时的形象表示:
Python源码剖析-深度探索动态语言核心技术_第17张图片

1.3 Python对象的多态性

Python利用C语言实现了对象的多态性,Python内部在创建对象时会使用 PyObject * 取保存和维护这个对象,(所有对象的头部是相同的),因此直接可以使用该指针所指对象的 ob_type 域动态去判断,正是这个域的存在,Python实现了多态性。

我们来分析一下:

void Print(PyObject* object) {
	object->ob_type.tp_print(object);
}

如果指针本身是一个PyIntObject* ,就会调用到 PyIntObject的类型对象中定义的输出操作,
如果是一个PyStringObject*, 就会调用到 PyStringObject 对象对应的类型对象中定义的输出操作。

1.4 引用计数

Python内建了垃圾回收机制,进行较为繁重的内存管理工作,引用计数正是Python垃圾回收机制的一部分。

  • Python中每一个东西都有一个 ob_refcnt 变量,维护着引用计数,决定着对象的创建和消亡。
  • 通过 Py_INCREF(op) 和 Py_DECREF(op) 两个宏来增加和减少一个对象的引用计数。
  • 当引用计数为0,会调用该对象的 tp_dealloc 进行析构动作

注意: 析构函数并不意味着最终会释放内存, 频繁申请和释放会有效率上的问题,因此Python中大量采用了内存池的技术。

Python源码剖析-深度探索动态语言核心技术_第18张图片

1.5 Python 对象的分类

我们将 Python 对象从概念上大致分为5类:

  • Fundamental 对象: 类型对象
  • Numeric 对象: 数值对象
  • Sequence对象:容纳其他对象的序列集合对象
  • Mapping对象:关联对象
  • Internal对象: Python 虚拟机在运行时内部使用的对象

Python源码剖析-深度探索动态语言核心技术_第19张图片

第2章:Python中的整数对象

2.1 初识 PyIntObject 对象

在定义上,是对 c 语言 long 的扩展而已~
Python源码剖析-深度探索动态语言核心技术_第20张图片
对于其 ob_type 的指向,则是指向了 PyInt_Type
Python源码剖析-深度探索动态语言核心技术_第21张图片

在PyInt_Type 中保存了关于PyIntObject对象的丰富信息,不仅有其对象应占有的内存大小,文档信息,也包括了支持的操作。
Python源码剖析-深度探索动态语言核心技术_第22张图片

操作定义: 需要注意的是 int_as_number 这个域,他是一个PyNumberMethods对象,PyNumberMethods 有39个函数指针,定义了39种可选的操作(加减乘除),而对于 int_as_number 确定了对于一个整数对象,数值操作是如何进行的。

Python源码剖析-深度探索动态语言核心技术_第23张图片

小知识点: 在整数的相加 int_add 中检查了加法结果是否溢出,用了位运算。
原理:只有两个相同符号的整数相加时才会溢出, 一正一负相加不会溢出, 溢出后得到的结果符号一定与任意一个整数符号相反,即

x = a + b
if ((x^a) >= 0 || (x^b) >= 0)     # 和其中一个符号相同就算不溢出
  return False
return True

2.2 PyIntObject 对象的创建和维护

2.2.1 对象创建的3种途径

内建类型对象的tp_new, tp_init 操作来创建实例对象, 最终依旧是会调用Python为特定内建对象准备的
CAPI。 为了创建一个 PyIntObject对象,Python提供了3条途径。

你可能感兴趣的:(Python脚本核心编程,pycharm,ide,python)