扩展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,以及在寫擴展程序時,會遇到的坑,比如:異常處理、引用計數等等。
?
轉載于:https://www.cnblogs.com/jianmu/p/7345716.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的扩展Python模块系列(二)----一个简单的例子的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL按时间查找
- 下一篇: iOS XIB等比例适配