python源码剖析笔记
文章目录
- 第0章 准备工作
- 0.1 Python总体架构
- 0.1.1 左边(python提供的模块, 库和用户自定义模块)
- 0.1.2 右边(python的运行时环境)
- 0.1.3 中间(python的核心--解释器)
- 0.1.4 右边和中间的关系
- 0.2 python源代码的组织
- 0.2.1 下载python2.5源码
- 0.2.2 解压源码后的目录结构
- 0.2.2.1 主要目录说明
- 0.3 Unix/Linux环境下编译python
- 0.4 修改python源代码
- 0.4.1 输出python对象的接口
- 0.4.2 重定向标准输出
- 0.5 注意事项
- 第一章 python对象初探(20-06-01 *)
- 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 引用计数
- 第2章 python中的整数对象
- 2.1 初识`PyIntObject`对象
- 2.2 `PyIntObject`对象的创建和维护
- 2.2.1 对象创建的3中途径
- 2.2.2 小整数对象
- 2.2.3 大整数对象
- 2.2.4 添加和删除
- 2.2.4.1 使用小整数对象池
- 2.2.4.2 创建通用整数对象池
- 2.2.4.3 使用通用整数对象池
- FQA
第0章 准备工作
0.1 Python总体架构
0.1.1 左边(python提供的模块, 库和用户自定义模块)
概念 | 说明 |
---|---|
模块 | 单个文件的形式 |
库 | 应该指包和模块两种形式(这里暂时不清楚) |
自定义模块 | 略 |
0.1.2 右边(python的运行时环境)
概念 | 说明 |
---|---|
运行时状态(Current State of Python) | 维护了解释器在执行字节码时不同的状态(比如正常状态和异常状态)之间切换的动作. 可以看作一个有穷状态机. |
内存分配器(Memory Allocator) | 负责python中创建对象时, 对内存的申请工作, 实际上它就是python运行时与C中malloc的一层接口. |
对象/类型系统(Object/Type Structures) | 包含了python中存在的各种内建对象, 比如整数, list和dict, 以及各种用户自定义的类型和对象. |
0.1.3 中间(python的核心–解释器)
概念 | 说明 |
---|---|
箭头 | 指示了python运行过程中的数据流方向 |
Scanner | 对应词法分析, 将文件输入的python源代码 或 从命令行输入的一行行python代码切分为一个个的token |
Parser | 对应语法分析, 在Scanner的分析结果上进行语法分析, 建立抽象语法树(AST) |
Compiler | 是根据简历的AST生成指令集合 – python字节码(byte code), 就像java编译器和C#编译器所做的那样 |
Code Evaluator | 执行字节码 |
0.1.4 右边和中间的关系
关系 | 说明 |
---|---|
使用 | 解释器与右边的对象/类型系统, 内存分配器之间的箭头表示"使用"关系 |
修改 | 解释器与右边运行状态之间的箭头表示"修改"关系, 即python在执行的过程中会不断地修改当前解释器所处的状态, 在不同的状态之间切换 |
0.2 python源代码的组织
0.2.1 下载python2.5源码
- python2.5下载地址
0.2.2 解压源码后的目录结构
0.2.2.1 主要目录说明
概念 | 说明 |
---|---|
Lib | 该目录包含了Python自带的所有标准库, Lib中的库都是用Python语言编写的 |
Modules | 该目录中包含了所有用C语言编写的模块, 比如random, cStringIO等. Modules中的模块是那些对速度要求非常严格的模块,而有一些对速度没有太严格要求的模块, 比如os, 就是用python编写,并且放在Lib目录下的 |
Parser | 该目录中包含了python解释器中的Scanner和Parser部分, 即对python源代码进行词法分析和语法分析的部分. 除了这些, Parser目录下还包含了一些有用的工具, 这些工具能够根据python的语法自动生成python的词法和语法分析器, 与YACC非常类似 |
Objects | 该目录中包含了所有python的内建对象, 包括整数, list, dict等. 同时, 该目录还包括了python在运行时需要的所有的内部使用对象的实现 |
Python | 该目录下包含了python解释器中的Compiler和执行引擎部分,成python运行的核心所在 |
0.3 Unix/Linux环境下编译python
实验环境ubuntu 18.04
查看Python-2.5/Setup.py文件中的detect_tkinter函数, 来确定需要下载的tcl和tk版本
- 设置安装位置
export LOCAL=$HOME/.local
- 安装tcl
wget -c http://prdownloads.sourceforge.net/tcl/tcl8.4.20-src.tar.gz
tar xf tcl8.4.20-src.tar.gz
./unix/configure --prefix=$LOCAL
make -j4 # 四核处理器
make install
- 安装tk
wget -c http://prdownloads.sourceforge.net/tcl/tk8.4.20-src.tar.gz
tar xvf tk8.4.20-src.tar.gz
./unix/configure --prefix=$LOCAL
make -j4
make install
- 安装python
SVNVERSION="Unversioned directory" ./configure --prefix=$LOCAL --enable-shared --with-tcltk-includes="-I$LOCAL/include" --with-tcltk-libs="-L$LOCAL/lib -ltcl8.6 -L$LOCAL/lib -ltk8.6"
export LD_LIBRARY_PATH=$LOCAL/lib:$LD_LIBRARY_PATH
make -j4
make install
目录
目录 | 说明 |
---|---|
bin | 可执行文件 |
lib | 存放的是python的标准库; lib/python2.5/config下存放的是libpython2.5.a(用c语言对python扩展时需要用到这个静态库. |
include | 头文件 |
选项
选项 | 说明 |
---|---|
–prefix | python将被安装的目标路径 |
–enable-shared | 加此选项, 会编译成动态链接库. 不加此选项, bin目录下的python可执行文件是静态链接的 |
-I(大写的i) | 预处理时查找头文件的范围 |
-L | 用来告诉链接器到哪个路径下面去找动态链接库 |
-l(小写的L) | 用来指定链接额外的库 |
- 参考
- 源码编译Tkinter
- Building Python from Source
0.4 修改python源代码
0.4.1 输出python对象的接口
- 接口
[Python-2.5/Include/object.h]
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
- 修改
Python-2.5/Objects/intobject.c
文件
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)/* flags -- not used but required by interface */
{PyObject* str = PyString_FromString("I am in int_print"); // 用于从C中的原生字符数组中创建出python中的字符串对象/* 第二个参数指明的是输出目标. stdout指定了输出目标为标准输出. 命令行激活的python, 使用的是stdout, idle的输出目标不是stdout, 就不会显示信息*/PyObject_Print(str, stdout, 0); printf("\n");fprintf(fp, "%ld", v->ob_ival);return 0;
}
- 设置so动态库加载目录
export LD_LIBRARY_PATH=$LOCAL/lib:$LD_LIBRARY_PATH
- 编译python
# 第一次执行configure的时候, 需要添加--enable-shared, 才能编译成动态链接库
# SVNVERSION="Unversioned directory" ./configure --prefix=<python将被安装的目标路径> --enable-shared
make
- 将python的位置设置到环境变量中
# 设置以后, 可以直接使用python在命令中启动python交互界面
export PATH=$LOCAL/bin:$PATH
- 在当前目录启动python, 就会链接到当前目录的so动态库
0.4.2 重定向标准输出
- 重定向到文件
- 标准输出 sys.stdout 也是C中stdout所代表的系统标准输出.
- 第一次执行sys.stdout时, 输出到屏幕
- 第二次执行sys.stdout时, 输出重定向到my_stdout.txt文件
- 重定向到idle
- 修改
Python-2.5/Objects/intobject.c
文件
static PyObject *
int_repr(PyIntObject *v)
{ /* PyInt_AsLong 将python的整数对象转换为C中的int值.设置条件, 用来筛选出我们需要的信息*/ if(PyInt_AsLong(v) == -999) {PyObject* str = PyString_FromString("I am in int_repr");PyObject* out = PySys_GetObject("stdout");if (out != NULL) {PyObject_Print(str, stdout, 0); printf("\n");} } char buf[64];PyOS_snprintf(buf, sizeof(buf), "%ld", v->ob_ival);return PyString_FromString(buf);
}
- python交互界面
- idle
0.5 注意事项
Py_ssize_t
可以看作int
- 凡是以New结尾的API, 都以C++中的new操作符视之; 凡是以Malloc结尾的,都是以C中的malloc操作符视之。
- 在c语言中是怎么实现继承的?
第一章 python对象初探(20-06-01 *)
1.1 python内的对象
- 在 python中, 对象就是为C中的结构体在堆上申请的一块内存, 一般来说, 对象是不能被静态初始化的, 并且也不能在栈空间上生存。
- 唯一的例外就是类型对象, python中的所有的内建的类型对象(如整数类型对象, 字符串类型对象)都是被静态初始化的。
- 在python中,一个对象一旦被创建,它在内存中的大小就是不变的了。具有可变长度数据的对象只能在对象内维护一个指向一块可变大小的内存区域的指针。
- 对象的分类
对象 | 说明 |
---|---|
fundamental | 类型对象(type) |
numeric | 数值对象(integer, float, boolean) |
sequence | 容纳其它对象的序列集合对象(string, list, tuple, set) |
mapping | 字典对象, 类似C++中的map的关联对象(dict) |
internal | 内部对象, python虚拟机在运行时内部使用的对象(function, code, frame, module, method) |
1.1.1 对象机制的基石——PyObject
- 在python中, 所有的东西都是对象, 而所有的对象都拥有一些相同的内容, 这些内容在PyObject中定义, PyObject是整个python对象机制的核心。
- 查看
Python-2.5/Include/object.h
中的PyObject
typedef struct _object {PyObject_HEAD
} PyObject;
- 查看
Python-2.5/Include/object.h
中的PyObject_HEAD
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \struct _object *_ob_next; \struct _object *_ob_prev;#define _PyObject_EXTRA_INIT 0, 0,#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \_PyObject_HEAD_EXTRA \ // 双向链表, 垃圾回收需要用到Py_ssize_t ob_refcnt; \ // 引用计数struct _typeobject *ob_type; // 指向类型对象的指针, 决定了对象的类型
- 实际发布的python中, 是不会定义符号Py_TRACE_REFS的, PyObject的定义如下
typedef struct _object {int ob_refcnt;struct _typeobject *ob_type;
} PyObject;
- 在python中, 对象机制的核心: 一个是引用技术,一个是类型信息.
变量 | 说明 |
---|---|
ob_refcnt |
与python的内存管理机制有关, 它实现了基于引用计数的垃圾收集机制。 对于某一个对象A, 当有一个新的PyObject * 引用该对象时, A的引用计数应该增加;而当这个PyObject * 被删除时, A的引用计数应该减少。当A的引用计数减少到0时,A就可以从堆上被删除, 以释放出内存供别的对象使用
|
ob_type | 包含对象的类型信息, 用来指定一个对象类型的类型对象 |
PyObject
中定义了每一个python对象都必须有的内容, 这些内容将出现在每一个python对象所占有的内存的最开始的字节中。剩下的内存用来保存对象自己特殊的信息。
[intobject.h]
typedef struct {PyObject_HEAD // PyObjectlong ob_ival; // 保存整数值
} PyIntObject;
1.1.2 变长对象
- 表示变长对象的结构体
[object.h]
#define PyObject_VAR_HEAD \PyObject_HEADint ob_size; /* Number of items in variable part 元素的个数*/typedef struct {PyObject_VAR_HEAD
} PyVarObject;
- 从
PyObject_VAR_HEAD
的定义可以看出,PyVarObject
只是对PyObject
的一个扩展而已。 - 因此对于任何一个
PyVarObject
,其所占用的内存, 开始部分的字节的意义和PyObject
时一样的。 - 在python内部, 每一个对象都拥有相同的对象头部。这就使得在python中, 对对象的引用变得非常的统一, 我们只需要用一个
PyObject *
指针就可以引用任意的一个对象。 而不论该对象实际是一个什么对象。
- python中不同对象与PyObject、PyVarObject在内存布局上的关系
对象 | 说明 |
---|---|
定长对象 | 不同对象的占用的内存大小是一样的 |
变长对象 | 不同对象占用的内存可能是不一样的 |
1.2 类型对象
- 占用内存空间的大小是对象的一种元信息, 这样的元信息是与对象所属类型密切相关的, 因此它一定会出现在与对象所对应的类型对象中. 现在我们可以来详细考察一下类型对象_typeobject:
[object.h]
typedef struct _typeobject {PyObject_VAR_HEADchar *tp_name; /* For printing, in format "<module>.<name>" */int tp_basicsize, tp_itemsize; /* For allocation *//* Methods to implement standard operations */destructor tp_dealloc;printfunc tp_print;....../* More standard operations (here for binary compatibility) */hashfunc tp_hash;ternaryfunc tp_call;......} PyTypeObject;
- 类型名,
tp_name
, 主要是python内部以及调试的时候使用. - 创建该类型对象时分配内存空间大小的信息, 即
tp_basicsize
和tp_itemsize
. - 与该类型对象相关连的操作信息(就是诸如
tp_print
这样的许多的函数指针). - 类型的类型信息.
PyTypeObject
对应python中的类
1.2.1 对象的创建
- Python C API 创建
- 范型的API(或者称为AOL(Abstract Object Layer). 这类API都具有诸如PyObject_***的形式, 可以应用在任何Python对象身上
// API内部会有一整套机制确定最终调用的函数是哪一个
PyObject_Print(int object);
PyObject_Print(string object);
PyObject* intObj = PyObject_New(PyObject, &PyInt_Type); // 创造一个整数对象
- 类型相关的API(或者称为COL(Concrete Object Layer). 这类API通常只能作用在某一种类型的对象上, 对于每一种内建对象, Python都提供了这样的一组API.
// 创建一个值为10的整数对象
PyObject *intObj = PyInt_FromLong(10);
- 无论采用哪种C API, python内部最终都是直接分配内存, 因为python对于内建对象是无所不知的. 对于自定义的类A, python不可能事先提供PyA_New这样的API. 对于这种情况, python会通过A所对应的类型对象创建实例对象.
- 实际上, 在python完成运行环境的初始化之后, 符号"int"就对应着一个表示为<type ‘int’>的对象, 这个对象其实就是python内部的PyInt_Type.
''' 在new style class中, int是一个继承自object的类型, 类似于int对应着python内部的PyInt_Type, object在python内部则对应着PyBaseObject_Type
'''
int(10) # 实际上是通过PyInt_Type创建了一个整数对象
- 使用C++中定义
int
这种类型的方式, 以及在python内部, 这种继承关系是如何实现的。
说明:标上序号的虚线箭头代表了创建整数对像的函数调用流程, 首先PyInt_Type
(对应python中的int类)中的tp_new
会被调用, 如果这个tp_new
为NULL
(真正的PyInt_Type
中并不为NULL
, 这里只是举例说明这种情况), 那么会到tp_base
指定的基类中去寻找tp_new
操作,PyBaseObject_Type
(对应python中的object基类)的tp_new
指向了object_new
. 新式类中,所有的类都是以object
为基类的, 所以最终会找到一个不为NULL
的tp_new
. 在object_new
中, 会访问PyInt_Type
中记录的tp_basicsize
信息, 继而完成申请内存的操作。这个信息记录着一个整数对象应该占用多大内存, 在python源码中, 这个值被设置成了sizeof(PyIntObject)
. 在调用tp_new
完成“创建对象”之后, 流程会转向PyInt_Type
的tp_init
, 完成“初始化对象”的工作。对应到C++中,tp_new
可以视为new
操作符, 而tp_init
则可视为类的构造函数。(这里只说明了类型对象在实例对象创建过程中的作用,实际上, 会有些不同)
1.2.2 对象的行为
在Python-2.5/Include/object.h
文件中的函数PyTypeObject
中定义了大量的函数指针, 这些函数指针最终都会指向某个函数, 或者指向NULL. 这些函数指针可以视为类型对象中所定义的操作, 而这些操作直接决定着一个对象在运行时所表现出的行为.
- 比如
PyTypeObject
中的tp_hash
指明对于该类型的对象, 如何生成其hash值。我们看到tp_hash
是一个hashfunc
类型的变量, 在object.h
中,hashfunc
实际上是一个函数指针:typedef long (*hashfunc)(PyObject *)
- 在这些操作信息中, 有三组非常重要的操作族, 在
PyTypeObject
中, 它们是tp_as_number
、tp_as_sequence
、tp_as_mapping
. 他们分别指向PyNumberMethods
、PySequenceMethods
和PyMappingMethods
函数族。PyNumberMethods
函数族示例:
[object.h]
typedef PyObject * (*binaryfunc)(PyObject *, PyObject *);typedef struct {binaryfunc nb_add; // 指定了数值对象进行加法操作时的具体行为binaryfunc nb_subtract;...
} PyNumberMethods;
- 对于一种类型来说, 它完全可以同时定义三个函数族中的所有操作。 下面示例中, 既有数值对象的特性, 也有关联对象的特性:
1.2.3 类型的类型
- 通过
PyType_Type
来确定一个对象为类型对象
[typeobject.c]
/* 所有用户自定义class所对应的PyTypeObject对象都是通过PyType_Type这个对象创建的
PyTypeObject PyType_Type = {PyObject_HEAD_INIT(&PyType_Type)0, /* ob_size */"type", /* tp_name */sizeof(PyHeapTypeObject), /* tp_basicsize */sizeof(PyMemberDef), /* tp_itemsize */...
};
PyType_Type
与一般PyTypeObject
的关系
PyInt_Type
和PyType_Type
是怎么建立关系的。在python中, 每一个对象都将自己的引用计数、类型信息保存在开始的部分中。为了方便对这部分内存的初始化, python中提供了几个有用的宏:
[object.h]
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \struct _object *_ob_next; \struct _object *_ob_prev;#define _PyObject_EXTRA_INIT 0, 0,#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif /* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD \_PyObject_HEAD_EXTRA \Py_ssize_t ob_refcnt; \struct _typeobject *ob_type;#define PyObject_HEAD_INIT(type) \_PyObject_EXTRA_INIT \1, type,
PyObject
和PyVarObject
的定义
[object.h]
/* 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 \PyObject_HEAD \Py_ssize_t ob_size; /* Number of items in variable part */
#define Py_INVALID_SIZE (Py_ssize_t)-1/* 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*.*/
typedef struct _object {PyObject_HEAD
} PyObject; typedef struct { PyObject_VAR_HEAD
} PyVarObject;
- 以
PyInt_Type
为例, 可以更清晰地看到一般的类型对象和这个特立独行的PyType_Type
对象之间的关系
[intobject.c]
PyTypeObject PyInt_Type = {PyObject_HEAD_INIT(&PyType_Type) // *_ob_next 0, *_ob_prev 0, ob_refcent 1, ob_type &PyType_Type0, // ob_size 0"int",sizeof(PyIntObject),...
};
PyIntObject
typedef struct {PyObject_HEADlong ob_ival;
} PyIntObject;
- 整数对象运行时的图像表现
变量 | 说明 |
---|---|
ob_type | 指向对象的类型 |
tp_base | 指向对象的父类 |
1.3 Python对象的多态性
通过PyObject
和PyTypeObject
, python利用C语言完成了C++所提供的对象的多态的特性. 在python创建一个对象, 比如PyIntObject
对象时, 会分配内存, 进行初始化. 然后python内部会用一个PyObject *
变量来保存和维护这个对象. 其它对象也与此类似, 所以在python内部各个函数之间传递的都是一种泛型指针-PyObject *
. 这个指针所指的对象究竟是什么类型的, 只能从指针所指对象的ob_type域动态进行判断, 而正是通过这个域, python实现了多态机制.
- Print函数示例
如果传给Print
的指针是一个PyIntObject*
, 那么它就会调用PyIntObject
对象对应的类型对象中定义的输出操作, 如果指针是一个PyStringObject*
, 那么就会调用PyStringObject
对象对应的类型对象中定义的输出操作.
void Print(PyObject* object)
{object->ob_type->tp_print(object);
}
- Hash函数示例
[object.c]
long PyObject_Hash(PyObject *v)
{PyTypeObject *tp = v->ob_type;if (tp->tp-hash != NULL)return (*tp->tp_hash)(v); // tp前面的*, 有和没有都是一样的. -> 的优先级大于*...
}
1.4 引用计数
- c和c++, 可以由程序员申请和释放内存. 容易造成内存泄露
- python由语言本身负责内存的管理和维护, 即采用垃圾收集机制. 提高开发效率, 降低bug发生几率. 损失了一部分运行效率. python通过对一个对象的引用技术的管理来维护对象在内存中的存在与否. python中的每一个东西都是对象, 都有一个
ob_refcnt
变量. 这个变量维护着该对象的引用计数, 从而也最终决定着该对象的创建与消亡. - 在python中, 主要通过
Py_INCREF(op)
和Py_DECREF(op)
两个宏来增加和减少一个对象的引用计数. 当一个对象的引用技术减少到0之后,Py_DECREF
将调用该对象的析构函数(借用c++的术语, 实际是对象对应的类型对象中定义的函数指针tp_dealloc
来指定的)来释放该对象所占有的内存和系统资源. 但是调用析构函数并不意味着最终一定会调用free
释放内存空间, 如果真是这样的话, 那频繁地申请, 释放内存空间会使python的执行效率大打折扣. 一般来说, python中大量采用了内存对象池的技术, 使用这种技术可以避免频繁地申请和释放内存空间. 因此在析构时, 通常都是将对象占用的空间归还到内存池中. - 从python的对象体系来看, 各个对象提供了不同的对象销毁事件处理函数, 而事件的注册动作正是在各个对象对应的类型对象中静态完成的.
PyObject
中的ob_refcnt
是一个32位的整形变量.- 类型对象, 永远不会被析构. 每一个对象中指向类型对象的指针不被视为对类型对象的引用.
- 在每一个对象创建的时候, python提供了一个
_Py_NewReference(op)
宏来将对象的引用计数初始化为1. - python在最终发行时, 引用计数的宏所对应的实际代码.
[object.h]
#define _Py_NewReference(op) ((op)->ob_refcnt = 1)
#define _Py_dealloc(op) ((*(op)->ob_type->tp_dealloc)((PyObject *)(op)))
#define Py_INCREF(op) ((op)->ob_refcnt++)
#define Py_DECREF(op)if (--(op)->ob_refcnt != 0);else_Py_Dealloc((PyObject *)(op))
/* Macros to use in case the object pointer may be NULL; */
#define Py_XINCREF(op) if ((op) == NULL); else Py_INCREF(op)
#define Py_XDECREF(op) if ((op) == NULL); else PY_DECREF(op)
第2章 python中的整数对象
2.1 初识PyIntObject
对象
- python中的 “整数” 这个概念的实现是通过
PyIntObject
对象来完成的. - 对象分类
根据对象维护数据的可变性将对象分为: 可变对象、不可变对象
根据同一类型不同对象占用的内存大小是否相同可将对象分为: 定长对象、变长对象 PyIntObject
的元信息实际上保存在对应的类型对象PyInt_Type
中
[intobject.c]
PyTypeObject PyInt_Type = {PyObject_HEAD_INIT(&PyType_Type) // 初始化头部0, // 因为PyTypeObject是一个PyVarObject对象, 因此这里需要设置大小为0"int", // 用来打印的字段,比如我们type(3)返回的int字符串就是来自这里。sizeof(PyIntObject), // 对象基本大小0, // 如果是可变大小对象,这个字段是对象里面存储项的大小(destructor)int_dealloc, /* tp_dealloc */(printfunc)int_print, /* tp_print */0, /* tp_getattr */0, /* tp_setattr */(cmpfunc)int_compare, /* tp_compare */(reprfunc)int_repr, /* tp_repr */&int_as_number, /* tp_as_number 确定了对于一个整数对象, 这些数值操作应该如何进行*/ 0, /* tp_as_sequence */0, /* tp_as_mapping */(hashfunc)int_hash, /* tp_hash */0, /* tp_call */(reprfunc)int_repr, /* tp_str */PyObject_GenericGetAttr, /* tp_getattro */0, /* tp_setattro */0, /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |Py_TPFLAGS_BASETYPE, /* tp_flags */int_doc, /* tp_doc */0, /* tp_traverse */0, /* tp_clear */0, /* tp_richcompare */0, /* tp_weaklistoffset */0, /* tp_iter */0, /* tp_iternext */int_methods, /* tp_methods */0, /* tp_members */0, /* tp_getset */0, /* tp_base */0, /* tp_dict */0, /* tp_descr_get */0, /* tp_descr_set */0, /* tp_dictoffset */0, /* tp_init */0, /* tp_alloc */int_new, /* tp_new */(freefunc)int_free, /* tp_free */
};
方法 | 说明 |
---|---|
int_dealloc | PyIntObject对象的析构操作 |
int_free | PyIntObject对象的释放操作 |
int_repr | 转化成PyStringObject对象 |
int_hash | 获得HASH值 |
int_print | 打印PyIntObject对象 |
int_compare | 比较操作 |
int_as_number | 数值操作集合 |
int_methods | 成员函数集合 |
2.2 PyIntObject
对象的创建和维护
2.2.1 对象创建的3中途径
- 在python自身的实现中, 几乎都是调用C API来创建内建实例对象的。
- 示例
[intobject.h]
/*
* 其中PyInt_FromUnicode最终调用PyInt_FromString, 而PyInt_String最终调用PyInt_FromLong
*/
PyObject *PyInt_FromLong(long ival) PyObject *PyInt_FromString(char *s, char **pend, int base)#ifdef Py_USING_UNICODEPyObject *PyInt_FromUnicode(Py_UNICODE *s, int length, int base)
#endif
2.2.2 小整数对象
- 为了性能考虑,python中对小整数有专门的缓存池,这样就不需要每次使用小整数对象时去用malloc分配内存以及free释放内存。
- 小整数的范围默认设定为[-5, 257)
#ifndef NSMALLPOSINTS // Number_Small_Positive_Integers
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS // Number_Small_Negtive_Integers
#define NSMALLNEGINTS 5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that theycan be shared.The integers that are saved are those in the range-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; // 小整数对象的对象池, 为PyIntObject对象指针数组。[]符号的优先级大于*
#endif
2.2.3 大整数对象
- python运行环境将提供一块内存空间, 这些内存空间由这些大整数轮流使用。
[intobject.c]
#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))struct _intblock {struct _intblock *next;PyIntObject objects[N_INTOBJECTS];
};typedef struct _intblock PyIntBlock; // 这个结构里维护了一块内存(block), 其中保存了一些PyIntObject对象static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
2.2.4 添加和删除
- 详解PyInt_FromLong
[intobject.c]
PyObject* PyInt_FromLong(long ival)
{register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {v = small_ints[ival + NSMALLNEGINTS];Py_INCREF(v);
#ifdef COUNT_ALLOCSif (ival >= 0)quick_int_allocs++;elsequick_neg_int_allocs++;
#endifreturn (PyObject *) v;}
#endifif (free_list == NULL) {if ((free_list = fill_free_list()) == NULL)return NULL;}/* Inline PyObject_New */v = free_list;free_list = (PyIntObject *)v->ob_type;PyObject_INIT(v, &PyInt_Type);v->ob_ival = ival;return (PyObject *) v;
}
2.2.4.1 使用小整数对象池
如果NSMALLNEGINTS + NSMALLPOSINTS > 0
成立, 那么python认为小整数对象池机制被激活了, 此时会创建对象池(PyIntObject
指针数组). PyInt_FromLong
会首先检查传入的long值是否属于小整数的范围, 如果确实属于小整数, 一切就变得简单了, 只需要返回在小整数对象池中的对应的对象就可以了。
如果小整数对象池机制没有被激活, 或者传入的long值不是属于小整数, python就会转向由block_list
维护的通用整数对象池。 正如前面我们所描述的, python需要在通过free_list在某块block的objects中, 寻找一块可用于存储新的PyIntObject
对象的内存。
2.2.4.2 创建通用整数对象池
显然,当首次调用PyInt_FromLong
时,free_list必定为NULL, 这时python会调用fill_free_list
, 创建新的block, 从而也就创建了新的空闲内存。需要注意的是, python对fill_free_list
的调用不光会发生在对PyInt_FromLong
的首次调用时,在python运行期间, 只要所有的block的空闲内存都被使用完了, 就会导致free_list
变为NULL, 从而在下一次PyInt_FromLong
的调用时激发对fill_free_list
的调用。
[intobject.c]
static PyIntObject *
fill_free_list(void)
{PyIntObject *p, *q;/* Python's object allocator isn't appropriate for large blocks. */// 申请大小为sizeof(PyIntBlock)的内存空间, 并链接到已有的block list中p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));if (p == NULL)return (PyIntObject *) PyErr_NoMemory();((PyIntBlock *)p)->next = block_list;block_list = (PyIntBlock *)p;/* Link the int objects together, from rear to front, then returnthe address of the last int object in the block. */// 将PyIntBlock中的PyIntObject数组——objects-转变成单向链表p = &((PyIntBlock *)p)->objects[0];q = p + N_INTOBJECTS;while (--q > p)q->ob_type = (struct _typeobject *)(q-1);q->ob_type = NULL;return p + N_INTOBJECTS - 1;
}
在fill_free_list
中, 会首先申请一个新的PyIntBlock
结构,如图所示
这时block中的objects
还仅仅是一个PyIntObject
对象的数组, 接下来, python将objects
中的所有PyIntObject
对象通过指针依次连接起来, 从而将数组转变成一个单向链表。 python从objects数组的最后一个元素开始链接的过程, 在整个链接过程中, python使用了PyObject
中的ob_type
指针作为 连接指针。
2.2.4.3 使用通用整数对象池
FQA
PyIntObject
是怎么知道它的元信息为PyInt_Type
的?
对象是由类创建的, 在创建对象的过程, 就会赋给类型对象相应的元信息- PyObject中的双向链表是怎么用来进行垃圾回收的?
PyType_Type
和PyTypeObject
?
所有用户自定义class
所对应的PyTypeObject
对象都是通过PyType_Type
这个对象创建的- 静态的整数对象的定义
[intobject.h]
// python中的整数对象PyIntObject实际上就是对C中原生类型long的一个简单包装
typedef struct {PyObject_HEADlong ob_ival;
} PyIntObject;
- c语言中是怎么判断加法是否溢出的?
python源码剖析笔记相关推荐
- Python源码剖析笔记5-模块机制
本文简书地址: http://www.jianshu.com/p/14586ec50ab6 python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机 ...
- python源码剖析笔记1——Python对象初见
本文简书地址:http://www.jianshu.com/p/763f6cec7a9b 工作整两年了,用python最多,然而对于python内部机制不一定都清楚,每天沉醉于增删改查的简单逻辑编写, ...
- 《Python源码剖析》读书笔记
<Python源码剖析>电子书下载 http://download.csdn.net/detail/xiarendeniao/5130403 Python源码在官网有下载链接,用ctags ...
- python源码剖析代码例子_Python源码剖析笔记5-模块机制
python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析 ...
- Python猫荐书系统之四:《Python源码剖析》
大家好,新一期的荐书栏目如期跟大家见面了. 先来看看今天的主角是谁:<Python源码剖析--深度探索动态语言核心技术>,2008年出版,作者 @陈儒 ,评分8.7分. 是的,你没看错,出 ...
- Python源码剖析[16] —— Pyc文件解析
Python源码剖析[16] -- Pyc文件解析 2008-02-28 18:29:55| 分类: Python |举报 |字号 订阅 Python源码剖析 --Pyc文件解析 本文作者: Rob ...
- python源码剖析 豆瓣_在数据分析师的分析中豆瓣的书那些值得读
最近总是有人问我有什么书好推荐看看,特烦.但是看到那么多人问,看来挺多人有这个需求,便想了一下,如何通过数据分析找到值得看的书.通过爬取某个标签例如产品,运营获取对应已经打了标签的书,获取书对应的评分 ...
- Python发展的新时代—冯大辉先生谈《Python源码剖析》
Python 3.0 beta 1终于在 6 月 18 号发布了,依照Python一贯主张的简洁,标准,统一精神,Beta版的新特性让我们惊喜的发现,Python正如我们所期望的那样已经进入了一个蓬勃 ...
- Python源码剖析[1] —— 编译Python
[ 绝对原创,转载请注明出处] 注意 :第一部分Python总体架构采用了网络文档<The Architecture of Python>,这是网络上唯一可见的以剖析Python实现为己任 ...
- Python源码剖析2-字符串对象PyStringObject
二. 1.PyStringObject与 PyString_Type PyStringObject是变长对象中的不可变对象.当创建了一个PyStringObject对象之后,该对象内部维护的字符串就不 ...
最新文章
- windows xp开机音乐7秒_(老旧电脑的福音)win 10 ltsb2015开机只要7秒
- mapreduce mysql_MapReduce直接连接MySQL获取数据
- 【MFC系列-第20天】CDC绘图类成员介绍
- iframe 高度根据子页面来确定
- PHP文件系统-文件下载
- hibernate 多条件组合查询之sql拼接
- Python 2 结束支持,Ubuntu 20.04 LTS 继续进行将其移除的工作
- 连接mysql报错:Access denied for user ‘root’@‘localhost’(using password: YES)的解决方法
- 写bat脚本--2021年5月18日
- CentOS 6U7分区大于2TB的磁盘以及挂载大于16TB分区磁盘的解决方案
- android 清除activity栈
- Lisp自动画梯形_CAD lisp 求助一段代码实现自动画弧!
- 都在谈中台,究竟什么是中台?
- JefferyZhao教导我们...
- html css 写百度页面,Html+CSS练习(百度注册页面)--div布局实现
- cfdpost导出图片_科学网—去除 cfd post 输出eps文件中的莫名其妙的点 - 姚程的博文...
- python 3.8教程_Python 3.8 新功能全解
- 10款超级优雅的Go语言开发工具
- 手把手从零到有的个人网站开发
- OCUI界面设计:UIPickerView 拾取视图