zoukankan      html  css  js  c++  java
  • 扩展Python模块系列(二)----一个简单的例子

    本节使用一个简单的例子引出Python C/C++ API的详细使用方法。针对的是CPython的解释器。

     目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两个点之间的距离。

    可以在Python代码这样使用test模块:

    import test
    s = test.distance((0, 0, 0), (1, 1, 1))

    先上代码:

    [test.c]

    #include <Python.h>
    #include <math.h>
    
    static PyObject* distance(PyObject* self, PyObject* args)
    {
        double x0, y0, z0, x1, y1, z1;
    
        if (!PyArg_ParseTuple(args, "(ddd)(ddd)", &x0, &y0, &z0, &x1, &y1, &z1))
        {
            return NULL;
        }
        return PyFloat_FromDouble(sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) + (z0 - z1) * (z0 - z1)));
    }
    
    static PyMethodDef cformula_methods[] =
    {    
        { "distance", distance, METH_VARARGS, "Return the 2D Distance of 2 Points." },
        { NULL, NULL, 0, NULL },
    };
    
    PyMODINIT_FUNC inittest(void)
    {
        Py_InitModule3("test", cformula_methods, "Common test Written in C.");
    }

     [Source.cpp]

    #include <Python.h>
    
    PyMODINIT_FUNC inittest();
    
    int main()
    {
        //PyImport_AppendInittab("test", inittest);
        Py_Initialize(); 
        inittest();    
        PyRun_SimpleString("import test");
        PyRun_SimpleString("s = test.distance((0, 0, 0), (1, 1, 1))");
        PyRun_SimpleString("print s");
        return 0;
    }

    编译运行后,结果如下:

    如果希望将test模块打包为一个动态链接库,供其他Python程序使用,即打包为test.pyd,在其他Python程序中可以直接import test,就和正常的Python内建模块一样。

    步骤如下:

    1) 在test.c同级目录下,新建一个python文件setup.py

    2)setup.py:

    from distutils.core import setup, Extension 
    setup(ext_modules=[Extension('test', sources = ['test.c'])])

    3) 执行python命令: python setup.py build

     这种情况是因为没有指定C的编译器,本文使用VS2015提供的编译器,所以首先执行:SET VS90COMNTOOLS=%VS140COMNTOOLS%

    会在该目录下发现build/lib.win32-2.7中有一个test.pyd,这就是编译后的动态库,可以直接import

     实现细节:

    1. 任何扩展Python模块的C程序,一般只需要包含<Python.h>头文件即可,文档中这样描述:

    All function, type and macro definitions needed to use the Python/C API are included in your code by the following line:

    #include "Python.h"

    在包含了Python.h之后,隐含地会自动包含C语言的标准头文件<stdio.h>, <string.h>, <errno.h>, <limits.h>, <assert.h> and <stdlib.h>

    2. 本质上Python C API提供了对C函数的wrapper。假设有一个现成的C函数, int add(int a, int b), 想把它实现在Python的模块里,需要实现一个wrapper函数 static PyObject* addAB(PyObject* self, PyObject* args), 将args解析为两个整数a、b,然后调用add(a,b),将返回值打包为一个Python整型对象返回。

    static PyObject* distance(PyObject* self, PyObject* args)

    函数一定要声明为static,将其限定在此文件范围;参数self是Python内部使用的,遵循范式即可;参数args是函数的参数列表,是一个tuple。

    PyArg_ParseTuple(args, "(ddd)(ddd)", &x0, &y0, &z0, &x1, &y1, &z1)

    此函数将参数列表args解析为两个tuple, 每个tuple是三个double类型的元素。如果参数列表不符合"(ddd)(ddd)"这种形式,直接返回NULL。

    PyFloat_FromDouble

    此函数将一个C原生的double,封装Python 的PyFloatObject。

    3. 定义该模块对外提供的函数接口

    static PyMethodDef cformula_methods[]

    在此数据结构中定义模块test对外提供的函数接口,第一个参数"distance"是Python内部记录的,就是test.distance调用时,python查找的函数名;第二个参数distance是函数具体实现,本质是一个函数指针;第三个参数是METH_VARARGS告诉Python此函数一个典型的PyCFunction,参数为两个PyOBject*,参数2使用PyArg_ParseTuple来解析;最后一个是函数说明。

    4.定义模块初始化函数

    PyMODINIT_FUNC inittest(void)
    {
        Py_InitModule3("test", cformula_methods, "Common test Written in C.");
    }

    函数inittest必须这样定义,如果模块名为example,那么模块初始化函数为initexample。宏定义PyMODINIT_FUNC定义如下:

    #       if defined(__cplusplus)
    #               define PyMODINIT_FUNC extern "C" void
    #       else /* __cplusplus */
    #               define PyMODINIT_FUNC void
    #       endif /* __cplusplus */

    针对cpp的实现加了修饰extern "C"。

    Py_InitModule3创建模块test, 当import test时,Python解释器会找到inittest创建的模块test。

    5. 测试代码Source.cpp

    在Python虚拟机环境初始化 Py_Initialize()之后,调用inittest(),则会创建新模块test。

    如果希望在import test时才初始化模块test,那么在Py_Initialize()之前调用PyImport_AppendInittab("test", inittest); 然后再注释掉initest()。

    PyImport_AppendInittab:Add a single module to the existing table of built-in modules.The new module can be imported by the namename, and uses the function initfunc as the initialization function called on the first attempted import. This should be called before Py_Initialize().

    本节通过一个简单的例子来展示了如何用C语言扩展Python模块,并简单解释了用到的Python C/C++ API。在下一节中,将更加深入地了解Python C/C++ API,以及在写扩展程序时,会遇到的坑,比如:异常处理、引用计数等等。

    作者: 建木
    出处: http://www.cnblogs.com/jianmu/
    本文版权归作者和博客园所有,如有转载,需注明出处。

  • 相关阅读:
    Linux配置Nginx负载均衡
    linux虚拟机ping不通主机和外网(包括刚装系统遇到的一些问题)
    MySQL的主从复制
    ubuntu下安装php
    ubuntu下sudo apt-get update Sources 404 Not Found 解决方法
    java 中类的加载顺序
    PHP生成各种验证码和Ajax验证
    PHP算法之二分查找和顺序查找
    ThinkPHP中SQL调试方法
    mysql 处理中文乱码问题
  • 原文地址:https://www.cnblogs.com/jianmu/p/7345716.html
Copyright © 2011-2022 走看看