python3クイックスタート(XVII) - Python拡張モジュール開発

python3クイックスタート(XVII) - Python拡張モジュール開発

A、Python拡張モジュール

1、Python拡張モジュールの説明

PythonとC / C ++プログラムは、PythonのC API、SWIGなどの相互作用、SIP、種々の有する ctypesの、CPythonの、cffi、boost.python など。
Pythonは単に言語仕様であり、Cで書かれた多くの特定の実装、CPythonの標準Pythonは、存在し、Pythonスクリプトは、CPythonのバイトコードにコンパイルされる参照カウント、PythonとC / C ++混合プログラミングを使用して仮想マシン実行ガベージコレクションによって解釈されこれは、基本的にはCPythonインタプリタに基づいています。他の実装は、PythonのJython、IronPythonの、PyPy、Pyston、含まのJythonはJVMのガベージコレクションは、.NETプラットフォーム用のJavaプログラミング、IronPythonのと混合することができる使用して、Javaで書かれています。
Python拡張モジュールはPythonで記述することができ、あなたは/ C C ++コンパイラベースの言語を使用して拡張を書くことができます。スケーラビリティPython言語カスタマイズ可能なコードの再利用のメリットを実現することができた、新しい機能を追加する必要があります。

図2は、膨張の特性は、Pythonモジュール

(1)の計算パワー増強する
書き込みにC / C ++を使用して、Pythonの拡張モジュールを、そのコンピューティング性能は無視できると小さいインターフェースのクロス言語コミュニケーションのパフォーマンス損失のC / C ++と同じレベルであり、それは非常に良好な性能サポートを提供することができ、典型的科学技術計算のためのnumpyのパッケージとして、その基礎となる数学的計算は、同じレベルの性能であるサードパーティのライブラリを、と呼ばれます。
計算マルチコア使用(2)
GILを制御することにより、Pythonの拡張モジュールは、マルチコアCPUは、コンピューティングパワーを使用することができるが、複数のコアを使用してカスタマイズすることができるマルチスレッドシングルコアの純粋なPythonプログラム限界に限定されるものではありません。
(3)単離およびモジュラーシステムコンポーネント
各C / C ++関数を使用することによっては、関数の間の任意の状態を共有するために、良好な分離成分を達成するために、開発試験に寄与しないように、Pythonインタフェースを提供します。すべてのパラメータは、Pythonから渡されたためと、簡単に印刷すると、中断debugabilityが大幅に向上しています。
(4)サードパーティのライブラリを使用する
Python用のサードパーティのライブラリをサポートしていない、開発者はシステムのドッキングのために独自の拡張モジュールを記述する必要があります。しかし、現代の人気の大規模なライブラリは、多くのアプリケーションの品質を作ることは非常なOpenCVの、典型的なPyCUDAとして、改善されている、公式のPython拡張モジュールを持っています。

二、PythonのC API拡張

1、PythonのC APIの拡張機能の紹介

CPythonとは、PythonインタプリタのC言語の実装であるPython言語の正式な実装で、最も広く使用されているPythonインタプリタです。
Pythonの拡張モジュールプロセスのC / C ++インプリメンテーションは、以下の通りである:
(1)ヘッダはPython.hである
(2)C / C ++モジュールが実装する
C / Cで定義されている(3)++関数Pythonインタフェースマップテーブル
(4)初期化関数
(5)初期化モジュール
の(6)setup.py調製物
(7)は、拡張モジュールがコンパイル搭載しました

2、Pythonのヘッダファイル

Python.hヘッダファイルはCPythonのパーサーCPythonのAPIへのC / C ++モジュールのフックを含み、はPython.hヘッダが何らかの影響規格を定義している可能性があるため、はPython.hヘッダーファイルは、任意の標準ヘッダの前に書き込まれなければなりませんプリプロセッサマクロヘッダファイル。
Python.hファイルはPy_とのためのPython C API、メソッドと変数のPython C APIのプレフィックスのすべての定義のPyを、回避の混乱に、この接頭コードを使用しないようにしてください。
Python.hファイル、PyObject `という名前のPythonオブジェクトのAPI
、内存管理函数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の形式でのPythonのC API asdl.hで定義されていないブール型、ブール型を行いますtypedefを列挙{偽、真} BOOL; = 1 = 0、すなわち、偽、真。

C / C ++で書かれたモジュール3、

次のように加え、モジュール、加算と乗算演算を達成するための乗算演算は次のとおりです。

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

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

4、C / C ++モジュールパッケージ

次のようにPythonのC拡張機能の定義は、一般的です。

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

PythonのC関数の拡張モジュールは、静的関数の名前は任意であるが、一般modulename_functionname戻りPyObjectポインタ型と命名します。あなたは関数が値を返したくない場合は、Pythonはスクリプト層戻りなしと同等のマクロPy_RETURN_NONEを、定義されています。
次のようにC / 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);
}

定義5、Pythonインタフェース・マッピング・テーブル

Pythonは、以下のようにPyMethodDef構造が定義され、アレイPyMethodDefインタフェースマッピングテーブル構造です。

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

ml_name:Pythonプログラムに公開されている関数名。
ml_meth:関数ポインタ、他の箇所で定義される関数。
ml_flags:関数のシグネチャ、一般的にはMETH_VARARGSであり、あなたは、キーワードのパラメータを渡したい、あるいは操作がMET_KEYWORDSに行うことができるならば、あなたは任意のパラメータを受け入れない場合、あなたはそれをMETH_NOARGSに値を割り当てることができます。
ml_doc:直接NULLに割り当てることができ、文書の文字列関数、。
Pythonのインタフェースマッピングテーブルは、例えば、から成るNULLと0構造によって終了されなければなりません。

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に示すように、拡張モジュールの初期化

モジュールのインポート時に拡張モジュールの初期化機能は、CPythonのパーサと呼ばれています。Pythonのヘッダファイルが作業をエクスポートするPyMODINIT_FUNCを定義するように、初期化関数は、ライブラリを構築する必要性に由来し、初期化関数を定義するときに使用する必要があります。

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

py_InitModule3関数プロトタイプは以下の通りである:
PyObject* Py_InitModule3(char *name, PyMethodDef *methods, char *doc)
モジュール名:エクスポートされたモジュール名と、
module_methods:方法マッピングテーブルモジュール;
のドキュメンテーション文字列:注釈モジュール;
戻り値:戻り新しいモジュールオブジェクト
Py_InitModule関数プロトタイプは以下の通りである:
PyObject* Py_InitModule(char *name, PyMethodDef *methods)
名前:モジュール名
方法:モジュール機能の説明テーブルの
戻り値は:新しいモジュールオブジェクトを返し
、次のようにオペレータモジュールが初期化されます。

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

setup.pyスクリプトは、典型的には、以下の便利な機能を備え、拡張モジュールの構成を構築するために使用される:
(1)は、コンパイル時に特定のマクロ定義、コンパイラパラメータとして良好ビルドパラメータを提供する
(2)他のサードパーティライブラリを参照します
コンパイルされた(3)は、異なるプラットフォームは、LinuxおよびMacは、差別と区別
(4)より包括的なメタ情報を提供するために、
次のようにsetup.pyスクリプト:

# !/usr/bin/env python

from distutils.core import setup, Extension

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

8、Python拡張モジュールをコンパイルインストールさ

モジュールをコンパイルします。
python setup.py build
モジュールを取り付ける:
python setup.py install
Python拡張モジュールは、現在の仮想環境にインストールされます。
python setup.py build_ext --inplace
--inplaceソースファイル生成モジュールを表します

図9に示すように、拡張モジュール

import operator

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

# output:
# 10
# 100

10、GILとマルチスレッド

GILの制限は、このような例外処理など、いくつかのグローバル変数、インタプリタ内部のPythonがあるという理由だけで、多くのサードパーティのモジュールとPython API GILは、グローバル変数を使用しているため、Pythonは、しかし、このような改善は常にGILことができませんでしたマルチコアの直接の原因であります進歩へ。
Pythonで拡張モジュールは、PythonにCPU制御背面、及び電流Cが/ C ++コードも続けることができるように、GILレベルを解放することができます。しかし、任意のPythonのAPIは、このように前にGILを解放し、GILの制御下で行われなければならない呼び出すと、計算が完了した後、再適用GIL、戻り値と例外処理を計算集約型のタスクを実行します。
次のように最初の方法であります:

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

次のように第二の方法であります:

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

マルチコア計算方法は、小部分の各々は、実行中のスレッドに配置され、小さな複数の部分にタスクを分割することです。スレッドを算出する機能拡張モジュールを呼び出し、計算機能はGIL実際の計算を放出します。

11、例外処理

機能が失敗した場合、インタプリタCPythonの規則は、それぞれのPython sys.exec_type、sys.exec_valueとsys.exec_tracebackに対応し、誤差値(NULL)を返し、3つのグローバル静的変数が設けられます。まず、検出された機能は、単純に外れ値を返す他の関数を呼び出し、珍しいとグローバル変数を報告する必要があります。
PythonのC APIは、設定に多くの機能を定義し、さまざまな例外を確認:
(1)PyErr_SetString(PyObject* type, const char* message)
タイプが事前定義されたオブジェクト、例えばPyExc_ZeroDivisionError、異常図ためのCストリングの理由です。
(2)PyErr_SetObject(PyObject* type, PyObject* value)
タイプ例外タイプ、値が異常値である
(3)PyErr_Occurred()
例外かどうかをチェックする
例外は無視され、パーサに渡されないことである場合(4)PyErr_Clear()関数を呼び出すことができる
(5)すべてダイレクトコールのmalloc()またはのrealloc()関数はPyErr_NoMemory()を呼び出す必要があり、障害が発生し、戻り故障フラグ
共通例外の種類は、以下のとおり
PyExc_ZeroDivisionError:0によって    
IOエラー:PyExc_IOError    
PyExc_TypeError:エラーの種類、そのような間違った種類のパラメータとして    
PyExc_ValueError:値誤差の範囲    
実行時エラー:PyExc_RuntimeError    
PyExc_OSError:エラー、様々なOSとの相互作用

三、PythonのC APIの型変換

1、Pythonのオブジェクトが構築されました

Py_BuildValue PyObject関数はオブジェクトを作成するために使用することができ、次のように、その関数が宣言されます。
python3クイックスタート(XVII) -  Python拡張モジュール開発

2、基本的なデータ型

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クイックスタート(XVII) -  Python拡張モジュール開発
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が検索パスヘッダを指定するために使用することができ、ライブラリdirsには、ライブラリ検索パスを指定します。
`下線生成されたオブジェクト名の拡張モジュール名をSWIGとPythonの前に使用しなければならない
,通过swig生成的python文件是utils.py,模块对象名必须是’_utils’,否则无法顺利编译。<br/>在当前目录下编译生成Python模块:<br/>のpython setup.pyにbuild_ext --inplace <br/>当前目录下生成模块如下:<br/>x86_64-を_utils.cpython-37メートル-Linuxは、gnu.so`

6、Python拡張モジュール

import utils

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

# output:
# Hello SWIG

おすすめ

転載: blog.51cto.com/9291927/2450914