python c模块扩展及PyIntObj对象

这是一篇《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是不同的对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值