这是一篇《Python源码剖析》的阅读笔记。
源码位置:https://github.com/shazi129/hackpython.git
Python中一些模块是用C来实现的,所以我们也可以用C来实现一些自定义模块。在读《Python源码剖析》的过程中,使用一些自定义的模块可以很方便的了解其中一些原理,而不用每改一行代码都得编译整个Python的源码。
Python的C模块
用C实现自定义模块的流程是:
1. 封装自定义方法
2. 导出自定义方法
一、封装自定义方法
封装函数的定义为:
PyObject * methodName(PyObject* self, PyObject* args);
参数含义:
self, 类似有python类函数的self,这里不管它
args, 输入参数列表,它的实现我们暂时不管,了解如何从里面取参数就行了:PyArg_ParseTuple(args, "i|s",&x, &y),这意思就是,args中取出一个整数给x,一个字符串给y。就这么简单,通常如下使用:
if (!PyArg_ParseTuple(args, "i|s",&x, &y))
{
return Py_None;
}
返回值:
一个Python对象,通常用:Py_BuildValue来构建,例如return Py_BuildValue("i", z),当然没返回值的函数就是Py_None了。至于i是什么意思就不说了。
一个加法封装的示例:
PyObject * myAdd(PyObject* self, PyObject* args)
{
intx = 0;
int y = 0;
int z = 0;
if (!PyArg_ParseTuple(args,"i|i", &x, &y))
{
return Py_None;
}
z = x + y;
returnPy_BuildValue("i", z);
}
二、导出自定义方法
主要步骤就是将所有自定义的方法组成一个列表,然后导出列表
1. 列表格式:
static PyMethodDef myAddMethods[] =
{
{"add", myAdd, METH_VARARGS, "add method"},
{NULL, NULL}
};
每一项的含义为:python中的方法名(add);对应的自定义的方法(myAdd);METH_VARARGS表示C的调用方式,一般就写这个了;这个函数的__doc__("add method")。以NULL结尾。
2. 导出列表:
void initadd()
{
Py_InitModule("add", myAddMethods);
}
这里要注意的是:函数名必须为iniXX的形式,XX为模块名。Py_InitModule的第一个参数是模块名,第二个参数是模块函数列表。
PyIntObject对象
一、整数对象池
小整数池的出现是为了解决频繁申请销毁int对象的问题。
在程序中,经常会使用一些不大的整数。想想c中的for循环,对于python来说,每次循环都要为i申请和销毁一个PyIntObject结构体,这个消耗是比较大的。
小整数对象就是将一些较少的数的PyIntObject先申请出来,池子的大小定义在intobject.c中的NSMALLPOSINTS和NSMALLNEGINTS,在python2.7.5中,是257和5,也就是小整数的范围是[-5, 256]。
大整数对象池是由一串PyIntBlock的链表,每次申请的时候批量申请,也是一种优化方式。
让我们来hack一下这个大小整数对象池。
1. 先在源码里将NSMALLPOSINTS和NSMALLNEGINTS导出,更改源码Object/intobject.c:
在该文件中加入:
int small_pos_ints = NSMALLPOSINTS;
int small_neg_ints = NSMALLNEGINTS;
2. 找到block_list定义的位置,去掉static,让这个变量能在so中extern到,完成之后编译python源码。
3. 因为python头文件中没有定义PyIntBlock,所以自己定义一个:
struct _PyIntBlock {
struct _PyIntBlock *next;
PyIntObjectobjects[257];
};
typedef struct _PyIntBlock PyIntBlock;
4. 实现wrap方法:
externint small_neg_ints;
extern int small_pos_ints;
//print the range of small ints
void printRange()
{
if (small_neg_ints + small_pos_ints <=0)
{
printf("small int is noavailable\n");
return;
}
printf("small ints's range: -%d <=%d\n", small_neg_ints, small_pos_ints);
}
extern PyIntBlock * block_list;
void printBlockInfo()
{
int count = 0; // numberof PyIntBlock
PyIntBlock * p =block_list;
while (p != NULL)
{
++count;
p = p->next;
}
printf("block_listsize: %d\n", count);
}
5. 封装:
PyObject * wrap_printInt(PyObject* self, PyObject* args)
{
printRange();
printBlockInfo();
return Py_None;
}
6. 导出就不写了。
7. 将其编译成hackint.so
运行结果:
这里可以看到小整数池和大整数池的大小。
二、一个问题
在看大整数对象时,曾经有个疑问,在python中:
a = 10000
b = 10000
a和b是不是一个对象呢, 我们来实现一个函数打印某个对象的地址:
PyObject * wrap_printAddr(PyObject* self,PyObject* args)
{
PyTupleObject * tupleArgs = (PyTupleObject *)args;
printf("arg size: %d\n", (int)tupleArgs->ob_size);
printf("first args addr: %p\n", *(tupleArgs->ob_item));
return Py_None;
}
看到结果:
可以看到,对于值相同小整数,始终指向一个地址,而每个大整数的地址都不同。结论:a和b是不同的对象