《python解释器源码剖析》第0章--python的架构与编译python
本系列是以陈儒先生的《python源码剖析》为学习素材,所记录的学习内容。不同的是陈儒先生的《python源码剖析》所剖析的是python2.5,本系列对应的是python3.7,所以某些地方会和原著有出入,另外我在介绍的过程中会穿插大量的python代码,不仅仅是介绍如何实现的,还会使用python实际地对我们的结论进行演示。下面就开始吧。不过在开始分析python的实现之前,我们有很多的准备工作要做。比如,首先应该了解一下python的整体架构,来对python的实现有一个宏观的认识
0.1 python的总体架构
废话不多说,先来看一张python的总体架构图。
如图所示。在最高的层次上,python的总体架构可以分为三个主要的部分。图的左边,是python所提供的的大量的模块、库、以及用户自定义的模块。比如在执行import os
的时候,这个os就是python内建的模块,当然用户还可以通过自定义模块来扩展python系统。
在图的的右边,是python的运行时环境,包括对象/类型系统(Object/Type Structures)、内存分配器(Memory Allocator)、和运行时状态信息(Current State of Python)。运行时状态
维护了解释器在执行字节码时不同的状态(比如正常状态和异常状态)之间切换的动作,我们可以将它视为一个巨大而复杂的有穷状态机。内存分配器则全权负责python中创建对象时,对内存的申请工作,实际上它就是python运行时与C中malloc的一层接口。而对象/类型系统则包含了python中存在的各种内建对象,比如:整数、list、dict,以及各种用户自定义的类型和对象。
在中间的部分,可以看到python的核心--解释器(interpreter),或者称之为虚拟机。在解析器中,箭头的方向指示了python运行过程中的数据流方向。其中scanner对应词法分析,将文件输入的python源代码或者从命令行输入的一行行python代码切分为一个个的token;parser对应语法分析,在scanner的分析结果上进行语法分析,建立抽象语法树(Abstract Syntax Tree,简称AST );Compiler则是对AST进行编译,生成指令集合--也就是python字节码(byte code);最后由Code Evaluator来执行这些字节码。因此Code Evaluator又可以被称为虚拟机。
Code Evaluator和Object/Type Structure之间的箭头表示使用关系;而与Current State of Python之间的箭头表示修改关系,即python在执行的过程中会不断地修改当前解释器所处的状态,在不同的状态之间切换。
0.2 python源代码的组织
python的源代码可以在python的官网www.python.org
中下载,下载完源码的压缩包并解压之后,可以看到如下的目录结构。
Include
:该目录包含了python所提供的的所有头文件,如果用户需要自己使用C或者C++来编写自定义模块扩展python,那么就需要用到这里的头文件Lib
:该目录包含了python自带的所有标准库,Lib中的库基本上都是使用python编写的Modules
:该目录中包含了所有用C语言编写的模块,比如random、io等。Modules中的模块是那些对速度要求非常严格的模块,而有一些对速度没有太严格要求的模块,比如os,就是用python编写,并且是放在Lib目录下的。Parser
:该目录中包含了python解释器中的Scanner和Parser部分,即对python源代码进行词法分析和语法分析的部分。除了这些,Parser还包含了一些有用的工具,这些工具能够根据python语言的语法自动生成python语言的词法和语法分析器,与YACC非常类似Objects
:该目录包含了所有python的内建对象,包括整数、list、tuple、dict、set等等。同时,该目录还包含了python在运行时需要的所有内部使用对象的实现。Python
:该目录包含了python解释器中Compiler(编译)和Code Evaluator(执行)两部分,是python运行的核心所在
0.3 修改python源代码
怎么上来就修改python源代码,别慌,只是简单的做一个小小的trick。
//文件:Objects/listobject.c。这是python中与list实现有关的源文件。
//可以看到这个函数主要用来改变列表的容量的。
static int
//关于这里的Py_ssize_t,这是python自定义的类型,就把它当成int即可
list_resize(PyListObject *self, Py_ssize_t newsize)
{
PyObject **items;
size_t new_allocated, num_allocated_bytes;
Py_ssize_t allocated = self->allocated;
//这里的allocated是当前list对象所拥有的容量,注意不是list对象的长度(或者说元素个数),而是容量
//比如 l = [1, 2, 3, 4, 5, 6, 7, 8],如果是使用[]这种中括号的方式创建列表的话。那么显然这里的l的长度和容量都为8。
//而这里的newsize是,当我们append或者extend新元素之后,对应的list对象的长度
//当使用l.append(9)的时候,说明了什么,说明了newsize变成了9
//可是原来的allocated(容量)是8啊,所以要进行扩容了。
//看这里的if条件,显然allocated >= newsize已经不满足了,因为要存储的元素的个数超过了容量,要扩容了
if (allocated >= newsize && newsize >= (allocated >> 1)) {
assert(self->ob_item != NULL || newsize == 0);
Py_SIZE(self) = newsize;
return 0;
}
//扩容之后的容量为new_allocated = newsize + newsize >> 3 + newsize < 9 ? 3 : 6
//所以将9带入进入,得到 9 + 1 + 6 = 16,这便是新分配的容量
/*
l = [1, 2, 3, 4, 5, 6, 7, 8]
print((l.__sizeof__() - 40) // 8) # 8
l.append(9)
print((l.__sizeof__() - 40) // 8) # 16
*/
//使用python进行测试也验证了这一点
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) {
PyErr_NoMemory();
return -1;
}
if (newsize == 0)
new_allocated = 0;
num_allocated_bytes = new_allocated * sizeof(PyObject *);
//将新添加的元素的指针加进列表里面
items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
if (items == NULL) {
PyErr_NoMemory();
return -1;
}
//让ob_item指向为新的items
self->ob_item = items;
Py_SIZE(self) = newsize;
//将这里的allocated变为新分配的容量
self->allocated = new_allocated;
return 0;
}
// 中间省略了一部分
//可以看到这是通过索引获取list对象内部元素的实现
static PyObject *
list_item(PyListObject *a, Py_ssize_t i)
{
//这里的i就是我们传入的索引。
//如果i小于0或者i大于op(对应的list对象)的最大索引
if (i < 0 || i >= Py_SIZE(a)) {
//当然python中list对象也支持负数索引,因此还会有其他的检测
if (indexerr == NULL) {
//PyUnicode_FromString可以理解为在python代码报错的时候,存储打印信息的函数
indexerr = PyUnicode_FromString(
//看这行代码熟悉不,小伙伴们,索引越界是不是报这个错误啊
"list index out of range");//我们将原本的信息改一下,改成"兄嘚,您列表索引越界了"
if (indexerr == NULL)
return NULL;
}
//设置异常,这是一个IndexError,indexerr则是异常信息
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
Py_INCREF(a->ob_item[i]);
//如果i为是正常索引,直接返回。
return a->ob_item[i];
}
0.4 编译python
编译python:只介绍如何在linux下编译python。编译的过程分为三步
进入python的主目录下,执行:./configure -prefix=你期望python安装到的路径
make
make install
其中2、3两步可以合为一步,即make && make install
,下面就用我们刚才修改完的python源码进行编译
可以看到异常,已经被我们从底层重新定义了。
0.5 注意事项
- 在早期版本,python的许多数值的类型都是int或者long,现在Python自己定义了一个新类型,Py_ssize_t,凡出现这个类型的地方,就把它当成long看待即可
- python的内存管理机制是比较复杂的,我们会在后面剖析。但是很快你就会看到对内存管理接口的使用。这是因为创建对象必先分配内存,而分配内存必须通过内存管理接口,所以我们在这里提一下。通常Python的源码中会使用PyObject_GC_NEW、PyObject_GC_Malloc、PyMem_MALLOC,PyObject_MALLOC等API。只要坚持一个原则即可,凡是以New结尾的,都当成是C++中new操作符即可,凡是以Malloc结尾的,都当成是C中的malloc操作符即可。
《python解释器源码剖析》第0章--python的架构与编译python的更多相关文章
- 《python解释器源码剖析》第12章--python虚拟机中的函数机制
12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python ...
- 《python解释器源码剖析》第9章--python虚拟机框架
9.0 序 下面我们就来剖析python运行字节码的原理,我们知道python虚拟机是python的核心,在源代码被编译成字节码序列之后,就将有python的虚拟机接手整个工作.python虚拟机会从 ...
- 《python解释器源码剖析》第13章--python虚拟机中的类机制
13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象 ...
- 《python解释器源码剖析》第1章--python对象初探
1.0 序 对象是python中最核心的一个概念,在python的世界中,一切都是对象,整数.字符串.甚至类型.整数类型.字符串类型,都是对象.换句话说,python中面向对象的理念观测的非常彻底,面 ...
- 《python解释器源码剖析》第11章--python虚拟机中的控制流
11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...
- 《python解释器源码剖析》第8章--python的字节码与pyc文件
8.0 序 我们日常会写各种各样的python脚本,在运行的时候只需要输入python xxx.py程序就执行了.那么问题就来了,一个py文件是如何被python变成一系列的机器指令并执行的呢? 8. ...
- 《python解释器源码剖析》第7章--python中的set对象
7.0 序 集合和字典一样,都是性能非常高效的数据结构,性能高效的原因就在于底层使用了哈希表.因此集合和字典的原理本质上是一样的,都是把值映射成索引,通过索引去查找. 7.1 PySetObject ...
- 《python解释器源码剖析》第4章--python中的list对象
4.0 序 python中的list对象,底层对应的则是PyListObject.如果你熟悉C++,那么会很容易和C++中的list联系起来.但实际上,这个C++中的list大相径庭,反而和STL中的 ...
- 《python解释器源码剖析》第2章--python中的int对象
2.0 序 在所有的python内建对象中,整数对象是最简单的对象.从对python对象机制的剖析来看,整数对象是一个非常好的切入点.那么下面就开始剖析整数对象的实现机制 2.1 初识PyLongOb ...
随机推荐
- JS原生上传大文件显示进度条-php上传文件
JS原生上传大文件显示进度条-php上传文件 在php.ini修改需要的大小: upload_max_filesize = 8M post_max_size = 10M memory_li ...
- iOS10权限问题
下图就是Info.plist的常用的权限问题: * 麦克风权限:Privacy - Microphone Usage Description 是否允许此App使用你的麦克风? * 相机权限: Priv ...
- Ansible变量嵌套解析
有时候需要用到ansible的变量的嵌套解析,就是“变量中嵌套变量”.例子如下: 假设有一个外部传递的变量,system_code = CRM,而我们同时有一系列类似的变量,如: ABS_port=1 ...
- C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)
1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...
- 网络实验 02-交换机的Telnet远程登录设置
交换机的Telnet远程登录设置 一.实验目标 掌握采用telnet方式配置交换机的方法 二.技术原理 1. 配置交换机的管理IP地址(计算机的IP地址与交换机管理IP地址在同一网段) 2. 为tel ...
- read: Connection reset by peer
造成此种问题的原因有多种,目前列下我所遇到过的. 1, sshfs usrname@172.23.65.122:/home/usrname ./122 在ubunutu 里面使用 sshfs 命令 映 ...
- eclipse SVN插件的日常使用
安装(我的安装方法,怎么方便怎么来) 1.打开eclipse,选择Help->Eclipse MarketPlace,搜索subclipse,点击install,等待.安装成功后会询问重启,点击 ...
- 【DSP开发】【计算机视觉】EMCV:可在DSP上运行的OpenCV
EMCV:可在DSP上运行的OpenCV EMCV项目主页: http://sf.net/projects/emcv EMCV全称为Embedded Computer Vision Library,是 ...
- 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念
linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- Linux 防火墙开放特定端口 (iptables)
1.查看状态:iptables -L -n2.直接编辑:vi /etc/sysconfig/iptables3.端口开放:-A INPUT -m state --state NEW -m tcp -p ...