Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)

时间:2022-06-09 03:08:58

    一直对Python扩展很感兴趣,刚好看到了Extending and Embedding the Python Interpreter文档,看的是最低版本(由于工作中用的是2.x, ̄□ ̄),官方文档

链接:http://docs.python.org/2.6/extending/index.html

   我使用的IDE是Code::Blocks 12.11,首先需要配置一下环境(windows)。

   由于需要调用Python提供的C API,需要设置incldue路径,和lib路径。

   打开Code::Blocks ->> Settings,

   我选择的编译器是GCC,最好看一下安装路径,选择的是CodeBlocks安装时的MinGW, 如果你以前安装过Qt等,可能会有所变化。

下面的编译器和连接器都是MinGW/bin目录下的。

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)


打开Search directories选项卡,找到Python安装路径下的include和libs目录,设置如下:

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)


Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)


还需要设置pythonlib文件。

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)

   环境配置好了,需要编码了,File ->> New ->> Project,在Projects的类型中我们选择Shared library(共享库),next之后语言选择C,Project title设为spam,下面都默认就可以了。

将下面代码覆盖main.c(我从文档中摘取的),

#include <Python.h>
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return Py_BuildValue("i", sts);
}

static PyMethodDef SpamMethods[] = {
    {"system",  spam_system, METH_VARARGS,"Execute a shell command."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initspam(void)
{
    (void) Py_InitModule("spam", SpamMethods);
}

 编译一下,如果看到下面输出,恭喜你,成功了。

-------------- Build: Debug in spam (compiler: GNU GCC Compiler)---------------

mingw32-gcc.exe -Wall  -g    -IC:\Python26\include  -c F:\Cython\spam\main.c -o obj\Debug\main.o
mingw32-g++.exe -shared -Wl,--output-def=bin\Debug\libspam.def -Wl,--out-implib=bin\Debug\libspam.a -Wl,--dll -LC:\Python26\libs  obj\Debug\main.o   -o bin\Debug\libspam.dll  C:\Python26\libs\python26.lib 
Creating library file: bin\Debug\libspam.a
Output size is 34.08 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings (0 minutes, 0 seconds)
 
   这是你会在你的bin/debug(release)下发现下面几个文件。

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)

     这个libspam.dll就是我们需要的动态库,将它改名为spam.pyd(python中的pyd其实就是dll),复制到你的python路径/libs目录下。

Ok,大功告成,下面就可以使用pyhon调用了。

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)

    到这里,有点小兴奋,原来扩展 这么简单啊。如果你觉得完全用C写的模块不太灵活,还可以简单的用Python包装一下,其实python中的很多标准库都是这么干的,比如socket模块。你打开python安装目录下的DLLs会发现_socket.pyd,那么在socket.py中肯定有类似下面的:

import _socket
from _socket import *

   到了这里,我们就来看看上面的代码蕴含了怎样的玄机。

#include <Python.h> 

   这个大家都懂的,需要注意一点的就是在Python.h中可能包含一些预处理指令会影响C的标准头文件,所以最好先声明python.h。而且Python.h包含了一些常用头文件,如stdio.h,string.h,errno.h,stdlib.h,所以偷懒点,就可以不声明其余头文件了。


    你会很好奇为什么会有一个self参数,还记得大一学数据结构时的链表吗,对链表操作的函数是不是第一个参数都是链表指针,这的self会被默认设置为NUllPyArg_ParseTuple函数会根据格式("s")检查args参数类型,并转换成C变量的值,很明显那个"s"应该是Python的string类型。Py_BuildValue和PyArg_ParseTuple功能相反,它会将C的值转换成Python的值,所以会将system的返回值sts转换成Python中的int类型。这也就是为什么上面例子调用echo语句成功返回0。


    PyMethodDef SpamMethods数组定义了需要导出到Python中的名字,函数指针,参数类型,描述信息。注意第三个参数,标志了函数的参数类型。
METH_VARARGS代表的就是我们写Python时的*args,而METH_KEYWORDS就是Python中的**kwargs,所谓的字典字典变量。描述信息就在Python中就是DocString了。

    最后就需要初始化该模块了,注意名字必须是initXXX,其中XXX就是我们所说的模块名。也就是说我们重命名的pyd文件名,initXXX和Py_InitModule(XXX)
三者必须一致。