十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問題一站解決
Python與C/C++交互的方案有多種,如Python C API,SWIG,SIP,ctypes,cpython,cffi,boost.python等。
Python只是一個(gè)語(yǔ)言規(guī)范,有很多具體實(shí)現(xiàn),CPython是標(biāo)準(zhǔn)Python,由C編寫,Python腳本被編譯成CPython字節(jié)碼,然后由虛擬機(jī)解釋執(zhí)行,垃圾回收使用引用計(jì)數(shù),Python與C/C++混合編程本質(zhì)是基于CPython解釋器。其它的Python實(shí)現(xiàn)包括Jython、IronPython、PyPy、Pyston,Jython是Java編寫的,使用JVM的垃圾回收,可以與Java混合編程,IronPython面向.NET平臺(tái)。
Python擴(kuò)展模塊可以用Python編寫,也可以用C/C++編譯型的語(yǔ)言來(lái)寫擴(kuò)展。Python可擴(kuò)展性具有為語(yǔ)言增加新功能、具有可定制性、代碼可以實(shí)現(xiàn)復(fù)用等優(yōu)點(diǎn)。
為寧夏等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及寧夏網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、寧夏網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
(1)提升計(jì)算能力
Python擴(kuò)展模塊使用C/C++編寫,其計(jì)算性能也是C/C++同級(jí)別的,其跨語(yǔ)言通信接口上的性能損失小到忽略不計(jì),所以能夠提供非常好的性能支持,典型如用于科學(xué)計(jì)算的Numpy包,其底層調(diào)用了第三方的數(shù)學(xué)計(jì)算庫(kù),其性能也是同級(jí)別的。
(2)使用多核心計(jì)算能力
Python擴(kuò)展模塊通過對(duì)GIL的控制,可以使用CPU的多核心計(jì)算能力,而不會(huì)受限于純Python程序的單核心限制,結(jié)合多線程可以定制使用多個(gè)核心。
(3)系統(tǒng)組件隔離和模塊化
通過把每個(gè)C/C++函數(shù)提供給Python接口,使得函數(shù)之間不共享任何狀態(tài),實(shí)現(xiàn)了良好的組件隔離,有助于開發(fā)和測(cè)試。同時(shí)由于參數(shù)全部通過Python傳遞,易于打印和中斷,可調(diào)試性有很大的提高。
(4)使用第三方庫(kù)
對(duì)于不支持Python的第三方庫(kù),需要開發(fā)者自己編寫擴(kuò)展模塊實(shí)現(xiàn)系統(tǒng)對(duì)接。但現(xiàn)代流行的大型庫(kù),很多都有官方的Python擴(kuò)展模塊,使得應(yīng)用質(zhì)量有了較大提高,典型如OpenCV和PyCUDA。
CPython是C語(yǔ)言實(shí)現(xiàn)的Python解釋器,是Python語(yǔ)言的官方實(shí)現(xiàn),是使用最廣泛的Python解釋器。
C/C++實(shí)現(xiàn)Python擴(kuò)展模塊的流程如下:
(1)包含頭文件Python.h
(2)C/C++模塊實(shí)現(xiàn)
(3)定義C/C++函數(shù)的Python接口映射表
(4)初始化函數(shù)
(5)初始化模塊
(6)setup.py編寫
(7)擴(kuò)展模塊編譯安裝
Python.h頭文件包含用于將C/C++模塊hook到CPython解析器的CPython API,而且必須將Python.h頭文件寫在任何標(biāo)準(zhǔn)頭文件前,因?yàn)镻ython.h頭文件可能定義了一些影響標(biāo)準(zhǔn)頭文件的預(yù)處理宏。
Python.h文件定義了所有的Python C API,Python C API的方法與變量前綴為Py_和Py,在代碼中盡量不要使用此前綴,避免混亂。
Python.h文件中,Python對(duì)象API命名為`PyObject、內(nèi)存管理函數(shù)API命名為
PyMen_、數(shù)值(包括整數(shù)和浮點(diǎn)數(shù)的運(yùn)算等)API命名為
PyNumber*、浮點(diǎn)數(shù)API命名為
PyFloat、整數(shù)API命名為
PyLong_、序列API命令為
PySequence*、列表API命名為
PyList、元組API命名為
PyTuple_、字典API命名為
PyDict*、集合API命名為
PySet、可迭代對(duì)象API命名為
PyIter_、字符串API命名為
PyUnicode*、函數(shù)參數(shù)API命名為
PyArg、函數(shù)API命名為
PyFunction_、文件對(duì)象API命名為
PyFile_*`。
C語(yǔ)言沒有bool類型,Python C API的bool類型定義在asdl.h中,形式如下:typedef enum {false, true} bool;,即false=0,true=1。
實(shí)現(xiàn)一個(gè)加法和乘法操作的模塊,加法和乘法操作方法如下:
static double add(double a, double b)
{
return a + b;
}
static double mul(double a, double b)
{
return a * b;
}
Python C 擴(kuò)展的函數(shù)定義一般如下:
static PyObject *MyFunction( PyObject *self, PyObject *args );
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );
Python C擴(kuò)展模塊中的函數(shù)是靜態(tài)函數(shù),名字是任意的,但通常命名為modulename_functionname的形式,返回PyObject類型的指針。如果函數(shù)不想返回一個(gè)值,Python定義了一個(gè)宏P(guān)y_RETURN_NONE,等價(jià)于在腳本層返回None。
C/C++函數(shù)的包裝如下:
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);
}
Python接口映射表是PyMethodDef結(jié)構(gòu)的數(shù)組,PyMethodDef結(jié)構(gòu)體定義如下:
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
ml_name:暴露給Python程序的函數(shù)名。
ml_meth: 函數(shù)的指針,即函數(shù)定義的地方。
ml_flags: 函數(shù)簽名方式,一般是METH_VARARGS;如果想傳入關(guān)鍵字參數(shù),可以與MET_KEYWORDS進(jìn)行或運(yùn)算;如果不接受任何參數(shù),可以給其賦值為METH_NOARGS。
ml_doc: 函數(shù)的文檔字符串,可以直接給其賦值為NULL。
Python接口映射表必須以一個(gè)由NULL和0組成的結(jié)構(gòu)體進(jìn)行結(jié)尾,示例如下:
static PyMethodDef operator_methods[] = {
{ "add", (PyCFunction)operator_add, METH_VARARGS, "operator add" },
{ "mul", (PyCFunction)operator_mul, METH_VARARGS, "operator mul" },
{ NULL, NULL, 0, NULL }
};
擴(kuò)展模塊的初始化函數(shù)會(huì)在模塊被導(dǎo)入時(shí)被CPython解析器調(diào)用。初始化函數(shù)需要從構(gòu)建的庫(kù)中導(dǎo)出,因此Python頭文件里定義了PyMODINIT_FUNC來(lái)進(jìn)行導(dǎo)出工作,因此需要在定義初始化函數(shù)時(shí)使用。
PyMODINIT_FUNC initModuleName() {
Py_InitModule3(ModuleName, module_methods, "docstring...");
}
py_InitModule3函數(shù)原型如下:PyObject* Py_InitModule3(char *name, PyMethodDef *methods, char *doc)
module_name: 被導(dǎo)出的模塊名;
module_methods: 模塊的方法映射表;
docstring: 模塊的注釋;
返回值:返回一個(gè)新的模塊對(duì)象
Py_InitModule函數(shù)原型如下:PyObject* Py_InitModule(char *name, PyMethodDef *methods)
name:模塊名稱
methods:模塊函數(shù)描述表
返回值:返回一個(gè)新的模塊對(duì)象
operator模塊的初始化如下:
PyMODINIT_FUNC initoperator()
{
Py_InitModule3("operator", operator_methods);
}
operator.c文件如下:
#include
/***************************
* C++語(yǔ)言函數(shù)定義
***************************/
static double add(double a, double b)
{
return a + b;
}
static double mul(double a, double b)
{
return a * b;
}
/*****************************
* C++語(yǔ)言函數(shù)的包裝
*****************************/
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);
}
setup.py腳本是用于構(gòu)建擴(kuò)展模塊的配置,通常包含如下實(shí)用功能:
(1)提供了更完善的編譯參數(shù),比如編譯時(shí)特定的宏定義,編譯器參數(shù)等
(2)引用其它第三方庫(kù)
(3)區(qū)分不同平臺(tái)的編譯,對(duì)Linux和Mac有所區(qū)分
(4)提供更加完善的元信息
setup.py腳本如下:
# !/usr/bin/env python
from distutils.core import setup, Extension
setup(name='operator', ext_modules=[Extension('operator', sources=['operator.c'])])
編譯模塊:python setup.py build
安裝模塊:python setup.py install
Python擴(kuò)展模塊將會(huì)安裝在當(dāng)前虛擬環(huán)境下。python setup.py build_ext --inplace
--inplace表示在源碼處生成模塊文件
import operator
if __name__ == '__main__':
result = operator.add(1, 9)
print(result)
result = operator.mul(100, 1)
print(result)
# output:
# 10
# 100
GIL是限制Python使用多核的直接原因,根本原因是Python解釋器內(nèi)部有一些全局變量,如異常處理等,但由于有很多Python API和第三方模塊在使用GIL全局變量,使得GIL的改進(jìn)一直得不到進(jìn)展。
在Python擴(kuò)展模塊層面是可以釋放GIL的,使得CPU控制權(quán)交還給Python,而當(dāng)前C/C++代碼也可以繼續(xù)執(zhí)行。但任何Python API的調(diào)用都必須在GIL控制下進(jìn)行,因此在執(zhí)行密集計(jì)算的任務(wù)前釋放GIL,完成計(jì)算后,重新申請(qǐng)GIL,再進(jìn)行返回值和異常處理。
第一種方法如下:
static PyObject *fun(PyObject *self, PyObject *args)
{
//.... ? ?
PyThreadState *_save; ? ?
_save=PyEval_SaveThread(); ? ?
block(); ? ?
PyEval_RestoreThread(_save);
//... }
}
第二種方法如下:
Py_BEGIN_ALLOW_THREADS;
//可能阻塞的操作
Py_END_ALLOW_THREADS;
使用多核計(jì)算的方法是,把任務(wù)拆分成多個(gè)小份,每個(gè)小份都放在一個(gè)線程中運(yùn)行。線程里調(diào)用擴(kuò)展模塊中的計(jì)算函數(shù),計(jì)算函數(shù)在實(shí)際計(jì)算時(shí)釋放GIL。
當(dāng)一個(gè)函數(shù)失敗時(shí),CPython解釋器約定會(huì)返回一個(gè)錯(cuò)誤值(NULL)并設(shè)置3個(gè)全局靜態(tài)變量,分別對(duì)應(yīng)Python的sys.exec_type, sys.exec_value和sys.exec_traceback。最先檢測(cè)到異常的函數(shù)應(yīng)該報(bào)告并設(shè)置全局變量,其它調(diào)用函數(shù)只是返回異常值。
Python C API定義了一些函數(shù)來(lái)設(shè)置并檢查各種異常:
(1)PyErr_SetString(PyObject* type, const char* message)
type是一個(gè)預(yù)定義的對(duì)象,例如PyExc_ZeroDivisionError,C字符串用于說明異常出現(xiàn)的原因。
(2)PyErr_SetObject(PyObject* type, PyObject* value)
type異常類型,value為異常值
(3)PyErr_Occurred()
用來(lái)檢查是否設(shè)置了一個(gè)異常
(4)如果要忽略一個(gè)異常而不傳遞給解析器,可以調(diào)用PyErr_Clear()函數(shù)
(5)所有直接調(diào)用malloc()或者realloc()函數(shù)失敗時(shí),必須要調(diào)用PyErr_NoMemory(),并且返回失敗標(biāo)志
常見異常類型如下:
PyExc_ZeroDivisionError :被0除 ? ?
PyExc_IOError :IO錯(cuò)誤 ? ?
PyExc_TypeError :類型錯(cuò)誤,如參數(shù)類型不對(duì) ? ?
PyExc_ValueError :值的范圍錯(cuò)誤 ? ?
PyExc_RuntimeError :運(yùn)行時(shí)錯(cuò)誤 ? ?
PyExc_OSError :各種與OS交互時(shí)的錯(cuò)誤
Py_BuildValue函數(shù)可以用于創(chuàng)建PyObject對(duì)象,其函數(shù)聲明如下:
Python提供了一系列的函數(shù)用于C++與Python數(shù)據(jù)類型的相互轉(zhuǎn)化,相應(yīng)函數(shù)的格式為PyXXX_AsXXX 或者PyXXX_FromXXX,一般帶有As的函數(shù)是將Python對(duì)象轉(zhuǎn)化為C++數(shù)據(jù)類型的,而帶有From的函數(shù)是將C++對(duì)象轉(zhuǎn)化為Python,Py的XXX表示的是Python中的數(shù)據(jù)類型。PyUnicode_AsWideCharString?將Python中的字符串轉(zhuǎn)化為C++中寬字符,而?PyUnicode_FromWideChar?是將C++的字符串轉(zhuǎn)化為Python中的字符串。Python3廢除了Python2中的普通的字符串,將所有字符串都當(dāng)做Unicode,所以使用Python3時(shí)需要將所有字符串轉(zhuǎn)化為Unicode。
#include
#include
int main() {
// 初始化Python環(huán)境
Py_Initialize();
// 有符號(hào)整型
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
// 無(wú)符號(hào)整型
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
// 長(zhǎng)整型
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
// 浮點(diǎn)類型
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轉(zhuǎn)換為utf8
PyObject* py_utf1 = PyUnicode_AsUTF8String(py_sval1);
PyObject* py_utf2 = PyUnicode_AsUTF8String(py_sval2);
// 將utf8轉(zhuǎn)換為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環(huán)境
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
PyObject* PyTuple_New(Py_ssize_t len)
創(chuàng)建一個(gè)Python元組對(duì)象,必須設(shè)置長(zhǎng)度,如果設(shè)置長(zhǎng)度為0,則元組對(duì)象是一個(gè)空元組。int PyTuple_Check(PyObject *p)
判斷是否是一個(gè)元組對(duì)象Py_ssize_t PyTuple_Size(PyObject *p)
獲取元組的大小PyObject* PyTuple_GetItem(PyObject* p, Py_ssize_t pos)
獲取元組內(nèi)指定下標(biāo)的值PyObject* PyTuple_GetSlice(PyObject* p, Py_ssize_t low, Py_ssize_t high)
獲取分片數(shù)據(jù) p[lwo, higt]
int PyTuple_SetItem(PyObject* p, Py_ssize_t pos, PyObject* o)
設(shè)置元組指定下標(biāo)的值int _PyTuple_Resize(PyObject *p, Py_ssize_t newsize)
改變?cè)M的大小
#include
#include
int main() {
// 初始化Python環(huán)境
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環(huán)境
Py_Finalize();
return 0;
}
PyObject* PyDict_New()
創(chuàng)建一個(gè)Python字典對(duì)象,成功返回新空字典,失敗返回NULL。int PyDict_Check(PyObject *p)
判斷對(duì)象是不是一個(gè)字典void PyDict_Clear(PyObject *p)
清空Python字典對(duì)象的數(shù)據(jù)int PyDict_Contains(PyObject* p, PyObject* key)
判斷字典內(nèi)是否存在一個(gè)鍵值數(shù)據(jù)PyObject* PyDict_Copy(PyObject* p)
拷貝一個(gè)字典的數(shù)據(jù),產(chǎn)生一個(gè)新的Python字典對(duì)象int PyDict_SetItem(PyObject* p, PyObject* key, PyObject *val)
給Python字典對(duì)象設(shè)置新的鍵值數(shù)據(jù)int PyDict_SetItemString(PyObject* p, const char key, PyObject *val)
給Python字典對(duì)象設(shè)置新的鍵值數(shù)據(jù)int PyDict_DelItem(PyObject* p, PyObject* key)
刪除Python鍵值數(shù)據(jù)int PyDict_DelItemString(PyObject* p, const char key)
刪除Python鍵值數(shù)據(jù)PyObject* PyDict_GetItem(PyObject* p, PyObject *key)
獲取Python字典對(duì)象的鍵的值PyObject* PyDict_GetItemString(PyObject* p, const char *key)
獲取Python字典對(duì)象的鍵的值PyObject* PyDict_SetDefault(PyObject* p, PyObject* key, PyObject* default)
設(shè)置Python字典對(duì)象的默認(rèn)值,當(dāng)獲取的Key不存在的時(shí)候則返回當(dāng)前的默認(rèn)數(shù)據(jù)PyObject* PyDict_Items(PyObject* p)
返回一個(gè)Python字典對(duì)象所有數(shù)據(jù)的PyListObjectPyObject* PyDict_Keys(PyObject* p)
返回一個(gè)Python字典對(duì)象的所有KeyPyObject* PyDict_Values(PyObject* p)
返回一個(gè)Python字典對(duì)象的所有Value數(shù)據(jù)Py_ssize_t PyDict_Size(PyObject *p)
獲取Python字典的大小 len(dict)int PyDict_Next(PyObject* p, Py_ssize_t ppos, PyObject* pkey, PyObject* pvalue)
遍歷獲取Python字典對(duì)象的所有數(shù)據(jù),
#include
#include
int main() {
// 初始化Python環(huán)境
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環(huán)境
Py_Finalize();
return 0;
}
PyObject* PyList_New(Py_ssize_t len)
創(chuàng)建一個(gè)列表,成功返回新列表,失敗返回NULLint PyList_Check(PyObject *p)
判斷是否是一個(gè)Python List(列表)Py_ssize_t PyList_Size(PyObject *list)
獲取列表元素的個(gè)數(shù) len(list)PyObject* PyList_GetItem(PyObject* list, Py_ssize_t index)
從列表里面獲取一個(gè)元素,計(jì)數(shù)器不會(huì)加1int PyList_SetItem(PyObject* list, Py_ssize_t index, PyObject* item)
設(shè)置別表指定位置的值,下標(biāo)的所在的位置必須是有值的,并且是有效的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)
獲取列表里面一段切片數(shù)據(jù),一段指定范圍的數(shù)據(jù) list[low:higt]int PyList_SetSlice(PyObject* list, Py_ssize_t low, Py_ssize_t high, PyObject* itemlist)
設(shè)置列表分片數(shù)據(jù),指定列表范圍的數(shù)據(jù) list[low:higt] = itemlistint PyList_Sort(PyObject *list)
對(duì)列表數(shù)據(jù)進(jìn)行排序int PyList_Reverse(PyObject *list)
把列表里面的所有數(shù)據(jù)反轉(zhuǎn)PyObject* PyList_AsTuple(PyObject* list)
將Python列表轉(zhuǎn)為Python元組 tuple(list)
#include
#include
int main() {
// 初始化Python環(huán)境
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環(huán)境
Py_Finalize();
return 0;
}
Python腳本調(diào)用擴(kuò)展模塊的函數(shù)時(shí),傳入的參數(shù)會(huì)存在PyObject* args
所指向的PyObject對(duì)象中,參數(shù)的提取使用PyArg_ParseTuple() 和 PyArg_ParseTupleAndKeywords()函數(shù)進(jìn)行解析 。int PyArg_ParseTuple(PyObject* arg, char* format, ...);
參數(shù) arg 是一個(gè)tuple對(duì)象,包含Python傳遞來(lái)的參數(shù), format 參數(shù)必須是格式化字符串。int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);
參數(shù) arg 是一個(gè)tuple對(duì)象,包含Python傳遞過來(lái)的參數(shù),參數(shù) kwdict 是關(guān)鍵字字典,用于接受運(yùn)行時(shí)傳來(lái)的關(guān)鍵字參數(shù)。參數(shù) kwlist 是一個(gè)NULL結(jié)尾的字符串,定義了可以接受的參數(shù)名,并從左到右與format中各個(gè)變量對(duì)應(yīng)。如果執(zhí)行成功 PyArg_ParseTupleAndKeywords() 會(huì)返回true,否則返回false并拋出異常。
format參數(shù)是一個(gè)字符串,通常每個(gè)字符代表一種類型;剩下的參數(shù)是與format相對(duì)應(yīng)的各個(gè)變量的地址,返回值是一個(gè)整型,解析成功返回1,解析出錯(cuò)返回0。
無(wú)參函數(shù)的參數(shù)提?。?br/>ok = PyArg_ParseTuple(args, "");
參數(shù)為一個(gè)字符串的函數(shù)的參數(shù)提取:ok = PyArg_ParseTuple(args, "s", &s);
參數(shù)為兩個(gè)長(zhǎng)整型與一個(gè)字符串的函數(shù)的參數(shù)提?。?br/>ok = PyArg_ParseTuple(args, "lls", &k, &l, &s);
參數(shù)至少有一個(gè)字符串,可以另外有一個(gè)字符串或整型的函數(shù)的參數(shù)提?。?br/>ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
參數(shù)為兩個(gè)元組的函數(shù)的參數(shù)提?。?br/>ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",&left, &top, &right, &bottom, &h, &v);
參數(shù)為一個(gè)PyObject對(duì)象,可以表示Python中的任意類型。ok = PyArg_ParseTuple(args, "O", &p);
void Py_Initialize()
初始化Python解釋器,在C++程序中使用其它Python C API前,必須初始化Python解釋器,如果調(diào)用失敗,將產(chǎn)生一個(gè)致命的錯(cuò)誤。int PyRun_SimpleString( const char *command)
執(zhí)行一段Python代碼。PyObject* PyImport_ImportModule( char *name)
導(dǎo)入一個(gè)Python模塊,參數(shù)name可以是.py文件的文件名。
`PyObject PyModule_GetDict( PyObject module)
PyObject PyRun_String( const char str, int start, PyObject globals, PyObject locals)
獲取模塊名稱空間下的字典對(duì)象
int PyArg_Parse( PyObject args, char format, ...)
執(zhí)行一段Python代碼。
PyObject PyObject_GetAttrString( PyObject o, char attr_name)
解析Python數(shù)據(jù)為C的類型
PyObject Py_BuildValue( char format, ...)
返回模塊對(duì)象o中的attr_name 屬性或函數(shù)
PyEval_CallObject(PyObject pfunc, PyObject pargs)
構(gòu)建一個(gè)參數(shù)列表,把C類型轉(zhuǎn)換為Python對(duì)象,使Python可以使用C類型數(shù)據(jù)
void Py_Finalize()`
pfunc是要調(diào)用的Python 函數(shù),通??捎肞yObject_GetAttrString()獲得;pargs是函數(shù)的參數(shù)列表,通??捎肞y_BuildValue()構(gòu)建
關(guān)閉Python解釋器,釋放解釋器所占用的資源。
調(diào)用Python模塊時(shí)需要首先包含Python.h頭文件,Python.h頭文件一般在安裝Python目錄中的 include文件中。
調(diào)用Python腳本前先調(diào)用Py_Initialize 函數(shù)來(lái)初始化Python環(huán)境,可以調(diào)用Py_IsInitialized來(lái)檢測(cè)Python環(huán)境是否初始化成功。
針對(duì)簡(jiǎn)單的Python語(yǔ)句,可以直接調(diào)用?PyRun_SimpleString?函數(shù)來(lái)執(zhí)行, 接收參數(shù)為Python語(yǔ)句的ANSI字符串,返回int型的值。如果為0表示執(zhí)行成功,否則為失敗。
(1)加載Python模塊(自定義模塊)
加載Python模塊需要調(diào)用?PyImport_ImportModule?函數(shù),傳入模塊名稱作為參數(shù),模塊名稱即py文件名稱,不能帶.py后綴。返回一個(gè)Python對(duì)象的指針,在C++中表示為PyObject,即模塊對(duì)象的指針。
(2)獲取Python函數(shù)對(duì)象
調(diào)用?PyObject_GetAttrString?函數(shù)來(lái)加載對(duì)應(yīng)的Python模塊中的方法,接收兩個(gè)參數(shù),第一個(gè)參數(shù)是獲取到的對(duì)應(yīng)模塊的指針,第二個(gè)參數(shù)是函數(shù)名稱的ANSI字符串。返回一個(gè)對(duì)應(yīng)Python函數(shù)的對(duì)象指針。
(3)檢查Python函數(shù)對(duì)象可調(diào)用性
調(diào)用?PyCallable_Check可以檢測(cè)Python函數(shù)對(duì)象是否可以被調(diào)用,接收參數(shù)為Python函數(shù)對(duì)象指針,如果能被調(diào)用會(huì)返回true否則返回false。
(4)參數(shù)傳遞
Python中函數(shù)的參數(shù)以元組的方式傳入,需要先將要傳入的參數(shù)轉(zhuǎn)化為元組。
(5)Python函數(shù)調(diào)用
調(diào)用?PyObject_CallObject?函數(shù)來(lái)執(zhí)行對(duì)應(yīng)的Python函數(shù),接收兩個(gè)參數(shù),第一個(gè)參數(shù)Python函數(shù)對(duì)象的指針,第二個(gè)參數(shù)是需要傳入Python函數(shù)中的參數(shù)組成的元組。返回Python的元組對(duì)象。
(6)解析返回值
獲取到返回值(Python元組對(duì)象)后使用對(duì)應(yīng)的函數(shù)將Python元組轉(zhuǎn)化為C++中的變量。
需要調(diào)用?Py_DECREF?來(lái)解除Python對(duì)象的引用,以便Python的垃圾回收器能正常的回收Python對(duì)象的內(nèi)存。
Py_Finalize();
util.py腳本如下:
def add(a, b):
return a + b
def mul(a, b):
return a * b
def power(a, b):
return a ** b
C++調(diào)用Python代碼如下:
#include
#include
int main(int argc, char** argv)
{
// 初始化Python
Py_Initialize();
// 檢查初始化是否成功
if (!Py_IsInitialized())
{
return -1;
}
// 添加當(dāng)前路徑
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;
}
// 參數(shù)進(jìn)棧
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));
// 調(diào)用Python函數(shù)
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);
// 關(guān)閉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是Python的一個(gè)可以鏈接C/C++的庫(kù),可以將C/C++函數(shù)編譯成動(dòng)態(tài)鏈接庫(kù),即window下的.dll文件或者是linux下的.so文件,通過使用cytpes可以直接調(diào)用動(dòng)態(tài)連接庫(kù)的C/C++函數(shù),加速代碼的運(yùn)行速度。
ctypes的優(yōu)點(diǎn)如下:
(1)不要修改動(dòng)態(tài)庫(kù)的源碼
(2)只需要?jiǎng)討B(tài)庫(kù)和頭文件
(3)使用比較簡(jiǎn)單,而且目前大部分庫(kù)都兼容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的接口,,其對(duì)應(yīng)的數(shù)據(jù)類型如下:
Python 中的類型,除了 None,int, long, Byte String,Unicode String 作為 C 函數(shù)的參數(shù)默認(rèn)提供轉(zhuǎn)換外,其它類型都必須顯式提供轉(zhuǎn)換。None:對(duì)應(yīng) C 中的 NULL,int、long對(duì)應(yīng) C 中的 int,具體實(shí)現(xiàn)時(shí)會(huì)根據(jù)機(jī)器字長(zhǎng)自動(dòng)適配。
在python3中,Byte String對(duì)應(yīng) C 中的一個(gè)字符串指針char *
,指向一塊內(nèi)存區(qū)域,通常字符串前面需要加小b,? b"helloworld";Unicode String對(duì)應(yīng) C 中一個(gè)寬字符串指針 wchar_t *
,指向一塊內(nèi)存區(qū)域,Python3中對(duì)應(yīng)的是字符串,如"helloworld"。
編寫C語(yǔ)言函數(shù)add.c文件:
#include
int add(int a, int b)
{
return a + b;
}
使用GCC編譯C語(yǔ)言文件:gcc -o libadd.so -shared -fPIC add.c
如果使用g++編譯生成C動(dòng)態(tài)庫(kù)的代碼中的函數(shù)時(shí),需要使用extern "C"來(lái)進(jìn)行編譯。
Python調(diào)用C語(yǔ)言動(dòng)態(tài)鏈接庫(kù):
import ctypes
if __name__ == '__main__':
loader = ctypes.cdll.LoadLibrary
lib = loader("./libadd.so")
result = lib.add(1, 2)
print(result)
# output:
# 3
需要extern "C"來(lái)輔助,也就是說還是只能調(diào)用C函數(shù),不能直接調(diào)用方法,但是能解析C++方法。如果不用extern "C",構(gòu)建后的動(dòng)態(tài)鏈接庫(kù)將沒有函數(shù)的符號(hào)表。
編寫C++語(yǔ)言函數(shù)add.cpp文件:
#include
#include
#include
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調(diào)用C++語(yǔ)言動(dòng)態(tài)鏈接庫(kù):
import ctypes
if __name__ == '__main__':
loader = ctypes.cdll.LoadLibrary
lib = loader("./libadd.so")
name = bytes("Bauer", 'utf-8')
lib.display(name, 28)
main.cpp文件:
#include
void display()
{
printf("Hello CPython\n");
}
int main(int argc, const char* argv[])
{
display();
return 0;
}
G++編譯:g++ -o main main.cpp
Python調(diào)用可執(zhí)行程序:
import os
if __name__ == '__main__':
os.system("./main")
SWIG(Simplified Wrapper and Interface Generator)是用來(lái)為腳本語(yǔ)言調(diào)用C和C++程序的軟件開發(fā)工具,實(shí)際上是一個(gè)編譯器,獲取C/C++的聲明和定義,用一個(gè)殼封裝起來(lái),以便其它腳本語(yǔ)言訪問。SWIG 最大的好處就是將腳本語(yǔ)言的開發(fā)效率和 C/C++ 的運(yùn)行效率有機(jī)的結(jié)合起來(lái)。
SWIG安裝:pip install swig
utils.h文件如下:
#include
class utils
{
public:
static void display();
};
utils.cpp文件如下:
#include "utils.h"
void utils::display()
{
printf("Hello SWIG\n");
}
swig封裝需要一個(gè).i后綴文件的封裝說明。%module <name>
為封裝名稱,Python調(diào)用包名是<name>
。
%{...%}內(nèi)是附加的函數(shù)說明和頭文件,源文件以外的部分都要包括在內(nèi),包括頭文件和宏定義等。
utils.i文件如下:
%module utils
%{
/* Includes the header in the wrapper code */
#include "utils.h"
%}
/* Parse the header file to generate wrappers */
%include "utils.h"
C++文件命令如下:swig -python -c++ utils.i
C文件命令如下:swig -python utils.i
swig會(huì)生成兩個(gè)不同的文件:utils_wrap.cxx(c源碼是utils_wrap.c)和python文件utils.py。
編寫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指定搜索的頭文件路徑,librarydirs指定搜索的庫(kù)路徑。
swig生成的擴(kuò)展模塊對(duì)象名必須使用python模塊名并在前面加上下劃線`,通過swig生成的python文件是utils.py,模塊對(duì)象名必須是’_utils’,否則無(wú)法順利編譯。
python setup.py build_ext --inplace
在當(dāng)前目錄下編譯生成Python模塊:
_utils.cpython-37m-x86_64-linux-gnu.so`
當(dāng)前目錄下生成模塊如下:
import utils
if __name__ == '__main__':
utils.utils_display()
# output:
# Hello SWIG