Python3 Quick Start (xvii) - Python extension module development

Python3 Quick Start (xvii) - Python extension module development

A, Python extension module

1, Python extension modules Description

Python and C / C ++ program has a variety of interactions, such as Python C API, SWIG, SIP, ctypes, cpython, cffi, boost.python like.
Python is just a language specification, there are many specific implementation, CPython standard Python, written by C, Python scripts are compiled into CPython bytecode is interpreted by a virtual machine executing garbage collection using the reference count, Python and C / C ++ mixed programming It is essentially based on CPython interpreter. Other implementations include Python Jython, IronPython, PyPy, Pyston, Jython is written in Java, using the JVM's garbage collection can be mixed with Java programming, IronPython for .NET platform.
Python extension modules can be written in Python, you can write extensions using C C ++ compiler-based language /. Scalability Python language have to add new features, having a customizable code reuse advantages can be realized.

2, the characteristics of expansion modules Python

(1) to enhance the computing power of
Python extension modules using C / C ++ to write, its computing performance is C / C ++ the same level of performance loss on its cross-language communication interface small to negligible, it can provide very good performance support, typical as Numpy package for scientific computing, its underlying mathematical calculation called third-party libraries, which is the same level of performance.
(2) using the multi-core computing
Python extension modules by controlling the GIL, multi-core CPU can use the computing power, but are not restricted to single-core pure Python program limits multithread can be customized using multiple cores.
(3) Isolation and modular system components
by using each C / C ++ function provides a Python interface, so as not to share any state between function, to achieve a good isolation components, and contribute to the development testing. And because all parameters passed by Python, easy to print and interrupted debugability have greatly improved.
(4) the use of third-party libraries
for Python does not support third-party libraries, developers need to write their own expansion modules for system docking. But modern popular large library, many have official Python extension modules, making application quality has been greatly improved, such as OpenCV and typical PyCUDA.

Two, Python C API extensions

1, Python C API extensions Introduction

CPython is a C language implementation of the Python interpreter, is the official implementation of the Python language, is the most widely used Python interpreter.
C / C ++ implementation of the Python extension module process is as follows:
(1) a header Python.h is
(2) C / C ++ module implements
(3) defined in C / C ++ function Python interface map table
(4) the initialization function
(5) Initialization module
(6) setup.py preparation of
(7) mounted expansion module compiled

2, Python header files

Python.h header file contains a C / C ++ module hook to CPython parser CPython API, and must be Python.h header file is written before any standard headers, because Python.h header may have defined some impact standards preprocessor macros header file.
Python.h file defines all of the Python C API, methods and variables Python C API prefix for the Py_ and Py, try not to use this prefix code to avoid confusion.
Python.h file, Python object API named PyObject `
、内存管理函数API命名为PyMen_、数值(包括整数和浮点数的运算等)API命名为 PyNumber * 、浮点数API命名为PyFloat 、整数API命名为PyLong_、序列API命令为 PySequence * 、列表API命名为PyList 、元组API命名为PyTuple_、字典API命名为 pydict * 、集合API命名为PySet 、可迭代对象API命名为PyIter_、字符串API命名为 PyUnicode * 、函数参数API命名为PyArg 、函数API命名为PyFunction_、文件对象API命名为 PyFile_ *`.
C does not bool type, bool type defined in asdl.h Python C API in the form as follows: typedef enum {false, true} bool ;, i.e., false = 0, true = 1.

3, written in C / C ++ module

Addition and multiplication operations to achieve a module, addition and multiplication operation is as follows:

static double add(double a, double b)
{
    return a + b;
}

static double mul(double a, double b)
{
    return a * b;
}

4, C / C ++ module packaging

Python C extension function definition is generally as follows:

static PyObject *MyFunction( PyObject *self, PyObject *args );
static PyObject *MyFunctionWithKeywords(PyObject *self,  PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );

Python C function extension module is a static function name is arbitrary, but generally named modulename_functionname returns PyObject pointer type. If you do not want the function returns a value, Python defines a macro Py_RETURN_NONE, equivalent to the script layer returns None.
Packaging C / C ++ function is as follows:

static PyObject* operator_add(PyObject *self, PyObject *args)
{
    float a, b;
    if(!PyArg_ParseTuple(args, "ff", &a, &b))
    {
        return NULL;
    }
    float result = add(a, b);
    return Py_BuildValue("f", result);
}

static PyObject* operator_mul(PyObject *self, PyObject *args)
{
    float a, b;
    if(!PyArg_ParseTuple(args, "ff", &a, &b))
    {
        return NULL;
    }
    float result = mul(a, b);
    return Py_BuildValue("f", result);
}

5, Python interface mapping table defined

Python is an array PyMethodDef interface mapping table structure, PyMethodDef structure is defined as follows:

struct PyMethodDef {
   char *ml_name;
   PyCFunction ml_meth;
   int ml_flags;
   char *ml_doc;
};

ml_name: function name exposed to Python programs.
ml_meth: a function pointer, a function that is defined elsewhere.
ml_flags: function signature, the general is METH_VARARGS; if you want to pass keyword parameters, or operation can be carried out with MET_KEYWORDS; if you do not accept any parameters, you can assign it a value for the METH_NOARGS.
ml_doc: document string functions, which can be directly assigned to NULL.
Python interface mapping table must be ended by a NULL and 0 structure composed of, for example:

static PyMethodDef operator_methods[] = {
   { "add", (PyCFunction)operator_add, METH_VARARGS, "operator add" },
   { "mul", (PyCFunction)operator_mul, METH_VARARGS, "operator mul" },
   { NULL, NULL, 0, NULL }
};

6, the expansion module initialization

Expansion module initialization function is called CPython parser when the module is imported. Initialization function is derived from the need to build the library, so Python header file defines PyMODINIT_FUNC to export the work, it is necessary to use when defining initialization function.

PyMODINIT_FUNC initModuleName() {
   Py_InitModule3(ModuleName, module_methods, "docstring...");
}

py_InitModule3 function prototype is as follows:
PyObject* Py_InitModule3(char *name, PyMethodDef *methods, char *doc)
module_name: exported module name;
module_methods: Method mapping table module;
the docstring of: annotation module;
Return Value: returns a new module object
Py_InitModule function prototype is as follows:
PyObject* Py_InitModule(char *name, PyMethodDef *methods)
name: Module name
methods: Module Function Description table
return value: returns a new module object
operator module is initialized as follows:

PyMODINIT_FUNC initoperator()
{
   Py_InitModule3("operator", operator_methods);
}
operator.c文件如下:
#include <Python.h>

/***************************
* C++语言函数定义
***************************/

static double add(double a, double b)
{
    return a + b;
}

static double mul(double a, double b)
{
    return a * b;
}

/*****************************
* C++语言函数的包装
*****************************/

static PyObject* operator_add(PyObject *self, PyObject *args)
{
    float a, b;
    if(!PyArg_ParseTuple(args, "ff", &a, &b))
    {
        return NULL;
    }
    float result = add(a, b);
    return Py_BuildValue("f", result);
}

static PyObject* operator_mul(PyObject *self, PyObject *args)
{
    float a, b;
    if(!PyArg_ParseTuple(args, "ff", &a, &b))
    {
        return NULL;
    }
    float result = mul(a, b);
    return Py_BuildValue("f", result);
}

static PyMethodDef operator_methods[] = {
   { "add", (PyCFunction)operator_add, METH_VARARGS, "operator add" },
   { "mul", (PyCFunction)operator_mul, METH_VARARGS, "operator mul" },
   { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initoperator()
{
   Py_InitModule3("operator", operator_methods);
}

7, setup.py written

setup.py scripts are used to build the configuration of the expansion module, typically comprising the following useful features:
(1) provides a better build parameters, such as specific macro definitions at compile time, the compiler parameters
(2) refer to other third-party libraries
(3) compiled distinguish different platforms, Linux and Mac differentiated
to provide more comprehensive meta-information (4)
setup.py script as follows:

# !/usr/bin/env python

from distutils.core import setup, Extension

setup(name='operator', ext_modules=[Extension('operator', sources=['operator.c'])])

8, Python extension module installed compiled

Compile the module:
python setup.py build
installing modules:
python setup.py install
Python extension modules will be installed in the current virtual environment.
python setup.py build_ext --inplace
--inplace represents the source file generating module

9, extension module

import operator

if __name__ == '__main__':
    result = operator.add(1, 9)
    print(result)
    result = operator.mul(100, 1)
    print(result)

# output:
# 10
# 100

10, GIL and multithreading

GIL limit is the direct cause of the multi-core Python, simply because there is an internal Python interpreter some global variables, such as exception handling, but because many third-party modules and Python API GIL use global variables, such improvements could not be always GIL to progress.
In Python extension modules can release the GIL level, so that the CPU control back to the Python, and the current C / C ++ code can also continue. But calling any Python API must be carried out under GIL control, thus freeing GIL before perform computationally intensive tasks, after the completion of the calculation, re-apply GIL, then the return value and exception handling.
The first method is as follows:

static PyObject *fun(PyObject *self, PyObject *args) 
{
    //....    
    PyThreadState *_save;    
    _save=PyEval_SaveThread();    
    block();    
    PyEval_RestoreThread(_save);
    //... }
}

The second method is as follows:

Py_BEGIN_ALLOW_THREADS; 
//可能阻塞的操作 
Py_END_ALLOW_THREADS;

Multicore calculation method is to split the task into a plurality of small parts, each of the small parts are placed in a running thread. Thread calls the function expansion module to calculate, calculate function releases the GIL actual calculation.

11, exception handling

When a function fails, the interpreter CPython convention will return an error value (NULL) and provided with three global static variables, respectively corresponding to the Python sys.exec_type, sys.exec_value and sys.exec_traceback. First detected function should report unusual and the global variable, call other function simply returns outliers.
Python C API defines a number of functions to set and check the various exceptions:
(. 1) PyErr_SetString(PyObject* type, const char* message)
type is a predefined object, e.g. PyExc_ZeroDivisionError, C strings reasons for abnormality FIG.
(2) PyErr_SetObject(PyObject* type, PyObject* value)
type exception type, value is an abnormal value
(3) PyErr_Occurred ()
to check if an exception
(4) If an exception is to be ignored and not passed to the parser may call PyErr_Clear () function
(5) All direct call malloc () or realloc () is the function fails, must call PyErr_NoMemory (), and returns failure flag
common exception types are as follows:
PyExc_ZeroDivisionError: by 0    
PyExc_IOError: IO error    
PyExc_TypeError: the type of error, such as the parameters of the wrong type    
PyExc_ValueError: value the range of error    
PyExc_RuntimeError: run-time error    
PyExc_OSError: error when interacting with various OS

Three, Python C API type conversion

1, Python objects built

Py_BuildValue PyObject function can be used to create an object, its function is declared as follows:
Python3 Quick Start (xvii) - Python extension module development

2, the basic data type

Python提供了一系列的函数用于C++与Python数据类型的相互转化,相应函数的格式为PyXXX_AsXXX 或者PyXXX_FromXXX,一般带有As的函数是将Python对象转化为C++数据类型的,而带有From的函数是将C++对象转化为Python,Py的XXX表示的是Python中的数据类型。PyUnicode_AsWideCharString 将Python中的字符串转化为C++中宽字符,而 PyUnicode_FromWideChar 是将C++的字符串转化为Python中的字符串。Python3废除了Python2中的普通的字符串,将所有字符串都当做Unicode,所以使用Python3时需要将所有字符串转化为Unicode。

#include <Python.h>
#include <stdio.h>

int main() {

    // 初始化Python环境
    Py_Initialize();

    // 有符号整型
    PyObject* py_ival1 = Py_BuildValue("i", -890);
    PyObject* py_ival2 = PyLong_FromLong(-890);
    int ival1 = PyLong_AsLong(py_ival1);
    int ival2 = PyLong_AsLong(py_ival2);
    printf("ival1 = %d, ival2 = %d\n", ival1, ival2);
    // ival1 = -890, ival2 = -890

    // 无符号整型
    PyObject* py_uval1 = Py_BuildValue("I", 123456789);
    PyObject* py_uval2 = PyLong_FromUnsignedLong(123456789);
    unsigned int uval1 = PyLong_AsUnsignedLong(py_uval1);
    unsigned int uval2 = PyLong_AsUnsignedLong(py_uval2);
    printf("uval1 = %d, uval2 = %d\n", uval1, uval2);
    // uval1 = 123456789, uval2 = 123456789

    // 长整型
    PyObject* py_lval1 = Py_BuildValue("L", 456784567845678);
    PyObject* py_lval2 = PyLong_FromLongLong(456784567845678);
    long long lval1 = PyLong_AsLongLong(py_lval1);
    long long lval2 = PyLong_AsLongLong(py_lval2);
    printf("lval1 = %lld, lval2 = %lld\n", lval1, lval2);
    // lval1 = 456784567845678, lval2 = 456784567845678

    // 浮点类型
    PyObject* py_fval1 = Py_BuildValue("f", 3.1415);
    PyObject* py_fval2 = PyFloat_FromDouble(3.1415);
    double fval1 = PyFloat_AsDouble(py_fval1);
    double fval2 = PyFloat_AsDouble(py_fval2);
    printf("fval1 = %f, fval2 = %f\n", fval1, fval2);
    // fval1 = 3.141500, fval2 = 3.141500

    // 布尔类型
    PyObject* py_bval1 = Py_BuildValue("b", false);
    PyObject* py_bval2 = PyBool_FromLong(true);
    int bval1 = PyLong_AsLong(py_bval1);
    int bval2 = PyLong_AsLong(py_bval2);
    printf("bval1 = %d, bval2 = %d\n", bval1, bval2);
    // bval1 = 0, bval2 = 1

    // 字符串类型
    PyObject* py_sval1 = Py_BuildValue("s", "hello world");
    PyObject* py_sval2 = PyUnicode_FromString("hello world");
    // 将unicode转换为utf8
    PyObject* py_utf1 = PyUnicode_AsUTF8String(py_sval1);
    PyObject* py_utf2 = PyUnicode_AsUTF8String(py_sval2);
    // 将utf8转换为const char*
    char* sval1 = PyBytes_AsString(py_utf1);
    char* sval2 = PyBytes_AsString(py_utf2);
    printf("sval1 = %s, sval2 = %s\n", sval1, sval2);
    // sval1 = hello world, sval2 = hello world

    // 退出Python环境
    Py_Finalize();
    return 0;
}

G++编译:
g++ -I/home/user/anaconda3/include/python3.7m -c pycobject.c
链接:
g++ -o main pycobject.o -L/usr/local/lib -lpython3.7 -lrt -lpthread -lutil -ldl

3、Python元组对象

PyObject* PyTuple_New(Py_ssize_t len)
创建一个Python元组对象,必须设置长度,如果设置长度为0,则元组对象是一个空元组。
int PyTuple_Check(PyObject *p)
判断是否是一个元组对象
Py_ssize_t PyTuple_Size(PyObject *p)
获取元组的大小
PyObject* PyTuple_GetItem(PyObject* p, Py_ssize_t pos)
获取元组内指定下标的值
PyObject* PyTuple_GetSlice(PyObject* p, Py_ssize_t low, Py_ssize_t high)
获取分片数据 p[lwo, higt]
int PyTuple_SetItem(PyObject* p, Py_ssize_t pos, PyObject* o)
设置元组指定下标的值
int _PyTuple_Resize(PyObject *p, Py_ssize_t newsize)
改变元组的大小

#include <Python.h>
#include <stdio.h>

int main() {

    // 初始化Python环境
    Py_Initialize();

    PyObject* tuple = PyTuple_New(3);
    PyObject* item0 = Py_BuildValue("i", 123);
    PyTuple_SetItem(tuple, 0, item0);

    PyObject* item1 = Py_BuildValue("s", "hello");
    PyTuple_SetItem(tuple, 1, item1);

    PyObject* item2 = Py_BuildValue("f", 23.98f);
    PyTuple_SetItem(tuple, 2, item2);

    PyObject* py_data = PyTuple_GetItem(tuple, 0);
    int value = PyLong_AsLong(py_data);
    printf("value = %d\n", value);
    // value = 123

    // 退出Python环境
    Py_Finalize();
    return 0;
}

4、Python字典对象

PyObject* PyDict_New()
创建一个Python字典对象,成功返回新空字典,失败返回NULL。
int PyDict_Check(PyObject *p)
判断对象是不是一个字典
void PyDict_Clear(PyObject *p)
清空Python字典对象的数据
int PyDict_Contains(PyObject* p, PyObject* key)
判断字典内是否存在一个键值数据
PyObject* PyDict_Copy(PyObject* p)
拷贝一个字典的数据,产生一个新的Python字典对象
int PyDict_SetItem(PyObject* p, PyObject* key, PyObject *val)
给Python字典对象设置新的键值数据
int PyDict_SetItemString(PyObject* p, const char key, PyObject *val)
给Python字典对象设置新的键值数据
int PyDict_DelItem(PyObject* p, PyObject* key)
删除Python键值数据
int PyDict_DelItemString(PyObject* p, const char key)
删除Python键值数据
PyObject* PyDict_GetItem(PyObject* p, PyObject *key)
获取Python字典对象的键的值
PyObject* PyDict_GetItemString(PyObject* p, const char *key)
获取Python字典对象的键的值
PyObject* PyDict_SetDefault(PyObject* p, PyObject* key, PyObject* default)
设置Python字典对象的默认值,当获取的Key不存在的时候则返回当前的默认数据
PyObject* PyDict_Items(PyObject* p)
返回一个Python字典对象所有数据的PyListObject
PyObject* PyDict_Keys(PyObject* p)
返回一个Python字典对象的所有Key
PyObject* PyDict_Values(PyObject* p)
返回一个Python字典对象的所有Value数据
Py_ssize_t PyDict_Size(PyObject *p)
获取Python字典的大小 len(dict)
int PyDict_Next(PyObject* p, Py_ssize_t ppos, PyObject* pkey, PyObject* pvalue)
遍历获取Python字典对象的所有数据,

#include <Python.h>
#include <stdio.h>

int main() {

    // 初始化Python环境
    Py_Initialize();

    PyObject* dict = PyDict_New();
    PyObject* key1 = Py_BuildValue("s", "age");
    PyObject* value1 = Py_BuildValue("i", 30);
    PyDict_SetItem(dict, key1, value1);

    PyObject* py_data = PyDict_GetItemString(dict, "age");
    int value = PyLong_AsLong(py_data);
    printf("value = %d\n", value);
    // value = 30

    // 退出Python环境
    Py_Finalize();
    return 0;
}

5、Python列表对象

PyObject* PyList_New(Py_ssize_t len)
创建一个列表,成功返回新列表,失败返回NULL
int PyList_Check(PyObject *p)
判断是否是一个Python List(列表)
Py_ssize_t PyList_Size(PyObject *list)
获取列表元素的个数 len(list)
PyObject* PyList_GetItem(PyObject* list, Py_ssize_t index)
从列表里面获取一个元素,计数器不会加1
int PyList_SetItem(PyObject* list, Py_ssize_t index, PyObject* item)
设置别表指定位置的值,下标的所在的位置必须是有值的,并且是有效的
int PyList_Insert(PyObject* list, Py_ssize_t index, PyObject* item)
在列表指定位置插入值
int PyList_Append(PyObject* list, PyObject* item)
在列表尾部追加值
PyObject* PyList_GetSlice(PyObject* list, Py_ssize_t low, Py_ssize_t high)
获取列表里面一段切片数据,一段指定范围的数据 list[low:higt]
int PyList_SetSlice(PyObject* list, Py_ssize_t low, Py_ssize_t high, PyObject* itemlist)
设置列表分片数据,指定列表范围的数据 list[low:higt] = itemlist
int PyList_Sort(PyObject *list)
对列表数据进行排序
int PyList_Reverse(PyObject *list)
把列表里面的所有数据反转
PyObject* PyList_AsTuple(PyObject* list)
将Python列表转为Python元组 tuple(list)

#include <Python.h>
#include <stdio.h>

int main() {

    // 初始化Python环境
    Py_Initialize();

    PyObject* list = PyList_New(5);
    PyObject* item0 = Py_BuildValue("i", 123);
    PyList_SetItem(list, 0, item0);

    PyObject* item1 = Py_BuildValue("s", "hello");
    PyList_SetItem(list, 1, item1);

    PyObject* item2 = Py_BuildValue("f", 23.98f);
    PyList_SetItem(list, 2, item2);

    PyObject* py_data = PyList_GetItem(list, 0);
    int value = PyLong_AsLong(py_data);
    printf("value = %d\n", value);
    // value = 123

    // 退出Python环境
    Py_Finalize();
    return 0;
}

6、参数提取

Python脚本调用扩展模块的函数时,传入的参数会存在PyObject* args所指向的PyObject对象中,参数的提取使用PyArg_ParseTuple() 和 PyArg_ParseTupleAndKeywords()函数进行解析 。
int PyArg_ParseTuple(PyObject* arg, char* format, ...);
参数 arg 是一个tuple对象,包含Python传递来的参数, format 参数必须是格式化字符串。
int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);
参数 arg 是一个tuple对象,包含Python传递过来的参数,参数 kwdict 是关键字字典,用于接受运行时传来的关键字参数。参数 kwlist 是一个NULL结尾的字符串,定义了可以接受的参数名,并从左到右与format中各个变量对应。如果执行成功 PyArg_ParseTupleAndKeywords() 会返回true,否则返回false并抛出异常。
format参数是一个字符串,通常每个字符代表一种类型;剩下的参数是与format相对应的各个变量的地址,返回值是一个整型,解析成功返回1,解析出错返回0。
无参函数的参数提取:
ok = PyArg_ParseTuple(args, "");
参数为一个字符串的函数的参数提取:
ok = PyArg_ParseTuple(args, "s", &s);
参数为两个长整型与一个字符串的函数的参数提取:
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s);
参数至少有一个字符串,可以另外有一个字符串或整型的函数的参数提取:
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
参数为两个元组的函数的参数提取:
ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",&left, &top, &right, &bottom, &h, &v);
参数为一个PyObject对象,可以表示Python中的任意类型。
ok = PyArg_ParseTuple(args, "O", &p);

四、C++调用Python脚本

1、Python C API常用接口

void Py_Initialize()
初始化Python解释器,在C++程序中使用其它Python C API前,必须初始化Python解释器,如果调用失败,将产生一个致命的错误。
int PyRun_SimpleString( const char *command)
执行一段Python代码。
PyObject* PyImport_ImportModule( char *name)
导入一个Python模块,参数name可以是.py文件的文件名。
`PyObject
PyModule_GetDict( PyObject module)<br/>获取模块名称空间下的字典对象<br/>PyObject PyRun_String( const char str, int start, PyObject globals, PyObject locals)<br/>执行一段Python代码。<br/>int PyArg_Parse( PyObject args, char format, ...)<br/>解析Python数据为C的类型<br/>PyObject PyObject_GetAttrString( PyObject o, char attr_name)<br/>返回模块对象o中的attr_name 属性或函数<br/>PyObject Py_BuildValue( char format, ...)<br/>构建一个参数列表,把C类型转换为Python对象,使Python可以使用C类型数据<br/>PyEval_CallObject(PyObject pfunc, PyObject pargs)<br/>pfunc是要调用的Python 函数,通常可用PyObject_GetAttrString()获得;pargs是函数的参数列表,通常可用Py_BuildValue()构建<br/>void Py_Finalize()`
关闭Python解释器,释放解释器所占用的资源。

2、Python环境初始化

调用Python模块时需要首先包含Python.h头文件,Python.h头文件一般在安装Python目录中的 include文件中。
调用Python脚本前先调用Py_Initialize 函数来初始化Python环境,可以调用Py_IsInitialized来检测Python环境是否初始化成功。

3、调用Python语句

针对简单的Python语句,可以直接调用 PyRun_SimpleString 函数来执行, 接收参数为Python语句的ANSI字符串,返回int型的值。如果为0表示执行成功,否则为失败。

4、调用Python函数

(1)加载Python模块(自定义模块)
加载Python模块需要调用 PyImport_ImportModule 函数,传入模块名称作为参数,模块名称即py文件名称,不能带.py后缀。返回一个Python对象的指针,在C++中表示为PyObject,即模块对象的指针。
(2)获取Python函数对象
调用 PyObject_GetAttrString 函数来加载对应的Python模块中的方法,接收两个参数,第一个参数是获取到的对应模块的指针,第二个参数是函数名称的ANSI字符串。返回一个对应Python函数的对象指针。
(3)检查Python函数对象可调用性
调用 PyCallable_Check可以检测Python函数对象是否可以被调用,接收参数为Python函数对象指针,如果能被调用会返回true否则返回false。
(4)参数传递
Python中函数的参数以元组的方式传入,需要先将要传入的参数转化为元组。
(5)Python函数调用
调用 PyObject_CallObject 函数来执行对应的Python函数,接收两个参数,第一个参数Python函数对象的指针,第二个参数是需要传入Python函数中的参数组成的元组。返回Python的元组对象。
(6)解析返回值
获取到返回值(Python元组对象)后使用对应的函数将Python元组转化为C++中的变量。

5、释放资源

需要调用 Py_DECREF 来解除Python对象的引用,以便Python的垃圾回收器能正常的回收Python对象的内存。

6、退出Python环境

Py_Finalize();

7、C++调用Python脚本实例

util.py脚本如下:

def add(a, b):
    return a + b

def mul(a, b):
    return a * b

def power(a, b):
    return a ** b

C++调用Python代码如下:

#include <Python.h>
#include <stdio.h>

int main(int argc, char** argv)
{
    // 初始化Python
    Py_Initialize();

    // 检查初始化是否成功
    if (!Py_IsInitialized())
    {
        return -1;
    }
    // 添加当前路径
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");

    // 载入Python模块
    PyObject* pName = PyUnicode_FromString("util");
    PyObject* pModule = PyImport_Import(pName);
    if (!pModule)
    {
        printf("can't find util.py\n");
        return -1;
    }
    PyObject* pDict = PyModule_GetDict(pModule);
    if ( !pDict )
    {
        return -1;
    }
    PyObject* result;
    int a = 0;
    int b = 0;
    PyObject* pFunc = PyDict_GetItemString(pDict, "add");
    if ( !pFunc || !PyCallable_Check(pFunc) )
    {
        printf("can't find function add\n");
        return -1;
    }

    // 参数进栈
    PyObject* pArgs;
    pArgs = PyTuple_New(2);
    a = 3;
    b = 4;
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",a));
    PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",b));

    // 调用Python函数
    result = PyObject_CallObject(pFunc, pArgs);
    printf("%d add %d == %d\n", a, b, PyLong_AsLong(result));

    pFunc = PyDict_GetItemString(pDict, "mul");
    if ( !pFunc || !PyCallable_Check(pFunc) )
    {
        printf("can't find function mul\n");
        return -1;
    }

    pArgs = PyTuple_New(2);
    a = 2;
    b = 3;
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",a));
    PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",b));

    result = PyObject_CallObject(pFunc, pArgs);
    printf("%d mul %d == %d\n", a, b, PyLong_AsLong(result));

    pFunc = PyDict_GetItemString(pDict, "power");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function power\n");
        getchar();
        return -1;
     }
    pArgs = PyTuple_New(2);
    a = 2;
    b = 3;
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",a));
    PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",b));
    result = PyObject_CallObject(pFunc, pArgs);
    printf("%d power %d == %d\n", a, b, PyLong_AsLong(result));

    Py_DECREF(pName);
    Py_DECREF(pArgs);
    Py_DECREF(pModule);

    // 关闭Python
    Py_Finalize();
    return 0;
}

G++编译:
g++ -I/home/user/anaconda3/include/python3.7m -c main.cpp
链接:
g++ -o main main.o -L/usr/local/lib -lpython3.7m -lrt -lpthread -lutil -ldl

五、ctypes扩展

1、ctypes简介

ctypes是Python的一个可以链接C/C++的库,可以将C/C++函数编译成动态链接库,即window下的.dll文件或者是linux下的.so文件,通过使用cytpes可以直接调用动态连接库的C/C++函数,加速代码的运行速度。
ctypes的优点如下:
(1)不要修改动态库的源码
(2)只需要动态库和头文件
(3)使用比较简单,而且目前大部分库都兼容C/C++

import platform
from ctypes import *

if __name__ == '__main__':
    if platform.system() == 'Windows':
        libc = cdll.LoadLibrary('msvcrt.dll')
    elif platform.system() == 'Linux':
        libc = cdll.LoadLibrary('libc.so.6')

    libc.printf(bytes('Hello world!\n', 'utf-8'))

 ctypes作为连接Python和C的接口,,其对应的数据类型如下:
Python3 Quick Start (xvii) - Python extension module development
Python 中的类型,除了 None,int, long, Byte String,Unicode String 作为 C 函数的参数默认提供转换外,其它类型都必须显式提供转换。None:对应 C 中的 NULL,int、long对应 C 中的 int,具体实现时会根据机器字长自动适配。
在python3中,Byte String对应 C 中的一个字符串指针char *,指向一块内存区域,通常字符串前面需要加小b,  b"helloworld";Unicode String对应 C 中一个宽字符串指针 wchar_t *,指向一块内存区域,Python3中对应的是字符串,如"helloworld"。

2、Python调用C动态连接库

编写C语言函数add.c文件:

#include <stdlib.h>

int add(int a, int b)
{
    return a + b;
}

使用GCC编译C语言文件:
gcc -o libadd.so -shared -fPIC add.c
如果使用g++编译生成C动态库的代码中的函数时,需要使用extern "C"来进行编译。
Python调用C语言动态链接库:

import ctypes

if __name__ == '__main__':
    loader = ctypes.cdll.LoadLibrary
    lib = loader("./libadd.so")
    result = lib.add(1, 2)
    print(result)

# output:
# 3

3、Python调用C++动态连接库

需要extern "C"来辅助,也就是说还是只能调用C函数,不能直接调用方法,但是能解析C++方法。如果不用extern "C",构建后的动态链接库将没有函数的符号表。
编写C++语言函数add.cpp文件:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

using namespace std;

class Utils
{
public:
    static void display(const char* name, int age)
    {
        printf("%s %d\n", name, age);
    }
};

extern "C"{
    void display(const char* name, int age)
    {
        Utils::display(name, age);
    }
}

G++编译:
g++ -o libadd.so -shared -fPIC add.cpp
Python调用C++语言动态链接库:

import ctypes

if __name__ == '__main__':
    loader = ctypes.cdll.LoadLibrary
    lib = loader("./libadd.so")
    name = bytes("Bauer", 'utf-8')
    lib.display(name, 28)

4、Python调用C/C++可执行程序

main.cpp文件:

#include <stdio.h>

void display()
{
    printf("Hello CPython\n");
}

int main(int argc, const char* argv[])
{
    display();
    return 0;
}

G++编译:
g++ -o main main.cpp
Python调用可执行程序:

import os

if __name__ == '__main__':
    os.system("./main")

六、SWIG扩展

1、SWIG简介

SWIG(Simplified Wrapper and Interface Generator)是用来为脚本语言调用C和C++程序的软件开发工具,实际上是一个编译器,获取C/C++的声明和定义,用一个壳封装起来,以便其它脚本语言访问。SWIG 最大的好处就是将脚本语言的开发效率和 C/C++ 的运行效率有机的结合起来。
SWIG安装:
pip install swig

2、编写C/C++模块

utils.h文件如下:

#include <stdio.h>

class utils
{
public:
    static void display();
};

utils.cpp文件如下:

#include "utils.h"

void utils::display()
{
    printf("Hello SWIG\n");
}

3、编写规则转换接口文件

swig封装需要一个.i后缀文件的封装说明。
%module &lt;name&gt;为封装名称,Python调用包名是&lt;name&gt;
%{...%}内是附加的函数说明和头文件,源文件以外的部分都要包括在内,包括头文件和宏定义等。
utils.i文件如下:

%module utils
%{
/* Includes the header in the wrapper code */
#include "utils.h"
%}
/* Parse the header file to generate wrappers */
%include "utils.h"

4、生成封装文件

C++文件命令如下:
swig -python -c++ utils.i
C文件命令如下:
swig -python utils.i
swig会生成两个不同的文件:utils_wrap.cxx(c源码是utils_wrap.c)和python文件utils.py。

5、编译生成模块

编写setup.py文件:

from distutils.core import setup, Extension

utils_module = Extension('_utils', sources=['utils.cpp', 'utils_wrap.cxx'])
setup(name='utils', version='0.1', author="bauer", description="""Simple swig C++/Python example""",
      ext_modules=[utils_module], py_modules=["utils"])

Include_dirs can be used to specify a search path headers, Library dirs specify the library search path.
swig generated object name extension module name and must be used in front of python underlined `
,通过swig生成的python文件是utils.py,模块对象名必须是’_utils’,否则无法顺利编译。<br/>在当前目录下编译生成Python模块:<br/> python setup.py build_ext --inplace <br/>当前目录下生成模块如下:<br/>_utils.cpython-37m-Linux-gnu.so` the x86_64-

6, Python extension module

import utils

if __name__ == '__main__':
    utils.utils_display()

# output:
# Hello SWIG

Guess you like

Origin blog.51cto.com/9291927/2450914