嵌套python解譯器(Embedding Python in Another Application)

來源:互聯網
上載者:User

hello,world

[dongsong@bogon python_study]$ cat py.cpp #include <Python.h>int main(int argc, char** argv){        Py_Initialize();        PyRun_SimpleString("import sys");        PyRun_SimpleString("sys.path.append('./')");        PyObject* pModule = PyImport_ImportModule("helloword");        PyObject* pFunc = PyObject_GetAttrString(pModule, "hello");        PyEval_CallObject(pFunc, NULL);        Py_Finalize();        return 0;}[dongsong@bogon python_study]$ cat helloword.pydef hello():        print 'hello,world!'[dongsong@bogon python_study]$ g++ -o py py.cpp  -I/home/dongsong/venv/include/python2.6/ -lpython2.6[dongsong@bogon python_study]$ ./pyhello,world!

官方原文檔
http://docs.python.org/3/extending/embedding.html
5. Embedding Python in Another Application

前面的章節我們討論了怎麼擴充Python,也就是,如果通過串連C函數庫來擴充Python的功能。另外還有一種方式可以做到這點:通過內嵌Python來豐富我們的C/C++應用。內嵌使得我們的程式可以用Python來實現某些功能,而不是只能使用C或者C++。這個有多種用途;一個例子是可以允許使用者通過編寫一些Python指令碼來定製我們需要的應用。如果某些功能用Python編寫更簡單的話,我們也可以考慮用它。

內嵌Python和擴充Python比較類似,但不全一樣。區別在於,當我們擴充Python的時候主程式還是Python解譯器,而如果我們內嵌Python,主程式就跟Python沒有關係了---相反,只是應用的某些部分偶爾調用Python解譯器來運行Python代碼。

所以如果我們要內嵌Python,我們需要提供我們自己的主程式。主程式需要做的事情之一就是初始化Python解譯器。最起碼,我們要調用函數Py_Initialize().我們也可以選擇把命令列參數傳到Python裡面去。然後我們可以從應用的任何地方調用解譯器。

這裡有幾種調用解譯器的不同方式:把包含Python語句的字串傳給PyRun_SimpleString();或者把一個stdio檔案指標和檔案名稱(僅僅為了錯誤資訊的識別)傳給PyRun_SimpleFile().我們也可以調用前面章節描述的低級(lower-level)操作來構建和使用Python對象。

5.1. Very High Level Embedding

最簡單的內嵌Python的形式是使用進階介面(the verf high level interface).這個介面執行Python指令碼,而不需要和應用直接互動。這適用於對檔案做操作的案例。

#include <Python.h>intmain(int argc, char *argv[]){  Py_SetProgramName(argv[0]);  /* optional but recommended */  Py_Initialize();  PyRun_SimpleString("from time import time,ctime\n"                     "print('Today is', ctime(time()))\n");  Py_Finalize();  return 0;}

Py_SetProgramName()應該在Py_Initialize()之前調用,用於把Python執行階段程式庫(Python run-time libraries)的路徑告訴解譯器。接下來,Python解譯器用Py_Initialize()初始化,再然後執行硬式編碼Python指令碼和輸出日期時間。接著,Py_Finalize()關閉解譯器,程式退出。對於一個實際程式,我們可能需要從另一個地方擷取python代碼,可能是一個文字編輯器,一個檔案,或者一個資料庫。從一個檔案擷取Python代碼的話使用PyRun_SimpleFile()更合適,它省掉了我們分配記憶體和讀取檔案內容的麻煩。

5.2. Beyond Very High Level Embedding: An overview

進階介面讓我們在應用中可以執行任意的程式碼片段,但是交換資料是相當麻煩的。如果有需求,我們應該使用低級介面(lower level calls)。雖然多寫了一些C代碼,但我們可以做到更多的事情。

注意,除了目的不一樣,擴充Python和內嵌Python還是挺相似的(is quite the same activity)。之前章節討論的大部分話題在這裡依然有效。為了展示這一點,考慮一下,擴充Python的C代碼實際上幹了什麼:

1.從Python到C轉換資料,

2.用轉換後的資料執行C程式的函數調用(Perform a function call to a C routine using the converted values),

3.把函數傳回值從C轉換到Python

內嵌Python,介面代碼要做的事情:

1.從C到Python轉換資料,

2.使用轉換後的資料執行Python介面程式的函數調用(Perform a function call to a Python interface routine using the converted values),

3.把函數傳回值從Python轉換到C

可以看到,資料轉換這一步只是對跨語言傳遞在不同方向上的簡單封裝。僅有的區別是在兩次資料轉換中間調用的程式。擴充時我們調用一個C程式,內嵌時我們調用一個Python程式。

這個章節我們不討論怎麼把資料從Python轉換到C或者反方向轉。引用的合理使用及錯誤處理也假定我們都明白了。因為這些跟擴充解譯器沒什麼區別,我們可以參考前面章節瞭解這些資訊。

5.3. Pure Embedding

第一個程式的目標是執行Python指令碼中的一個函數。和進階介面那部分講的一樣,Python解譯器不和應用直接互動(下一步講解的內容就要發生互動了)。

下述代碼執行Python指令碼中的函數:

#include <Python.h>intmain(int argc, char *argv[]){    PyObject *pName, *pModule, *pDict, *pFunc;    PyObject *pArgs, *pValue;    int i;    if (argc < 3) {        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");        return 1;    }    Py_Initialize();    pName = PyUnicode_FromString(argv[1]);    /* Error checking of pName left out */    pModule = PyImport_Import(pName);    Py_DECREF(pName);    if (pModule != NULL) {        pFunc = PyObject_GetAttrString(pModule, argv[2]);        /* pFunc is a new reference */        if (pFunc && PyCallable_Check(pFunc)) {            pArgs = PyTuple_New(argc - 3);            for (i = 0; i < argc - 3; ++i) {                pValue = PyLong_FromLong(atoi(argv[i + 3]));                if (!pValue) {                    Py_DECREF(pArgs);                    Py_DECREF(pModule);                    fprintf(stderr, "Cannot convert argument\n");                    return 1;                }                /* pValue reference stolen here: */                PyTuple_SetItem(pArgs, i, pValue);            }            pValue = PyObject_CallObject(pFunc, pArgs);            Py_DECREF(pArgs);            if (pValue != NULL) {                printf("Result of call: %ld\n", PyLong_AsLong(pValue));                Py_DECREF(pValue);            }            else {                Py_DECREF(pFunc);                Py_DECREF(pModule);                PyErr_Print();                fprintf(stderr,"Call failed\n");                return 1;            }        }        else {            if (PyErr_Occurred())                PyErr_Print();            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);        }        Py_XDECREF(pFunc);        Py_DECREF(pModule);    }    else {        PyErr_Print();        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);        return 1;    }    Py_Finalize();    return 0;}

上述代碼載入argv[1]指定的Python指令碼,並調用由argv[2]指定名稱的函數。函數的整型參數是argv數組的其他值。如果我們編譯和連結(compile and link,http://docs.python.org/3/extending/embedding.html#compiling)這個程式(最終的可執行檔我們命名為"call"),用它執行一個Python指令碼,比如:

def multiply(a,b):    print("Will compute", a, "times", b)    c = 0    for i in range(0, a):        c = c + b    return c

然後結果應該是:

$call multiply multiply 3 2Will compute 3 times 2Result of call: 6

儘管程式相對於它的功能來說夠大的,但是大部分的代碼是在做Python和C之間的資料轉換,和錯誤彙報。有趣的是它遵守了內嵌Python的規則,以下述代碼作為開頭(初始化解譯器):

Py_Initialize();pName = PyUnicode_FromString(argv[1]);/* Error checking of pName left out */pModule = PyImport_Import(pName);

初始化解譯器之後,指令碼用PyImport_Import()(http://docs.python.org/3/c-api/import.html#PyImport_Import)匯入。這個函數需要一個Python字串作為它的參數,該Python字串是用PyUnicode_FromString()(http://docs.python.org/3/c-api/unicode.html#PyUnicode_FromString)資料轉換函式來構建的。

pFunc = PyObject_GetAttrString(pModule, argv[2]);/* pFunc is a new reference */if (pFunc && PyCallable_Check(pFunc)) {    ...}Py_XDECREF(pFunc);

一旦指令碼被載入,需要要找的名字可以通過PyObject_GetAttrString()(http://docs.python.org/3/c-api/object.html#PyObject_GetAttrString)來獲得。如果名字存在,並且返回的對象是可調用的(callable),我們可以安全的認定它是個函數。然後程式就正常執行,構建一個元組參數。再然後,調用Python函數:

pValue = PyObject_CallObject(pFunc, pArgs);

函數返回後,pValue要嘛是NULL,要嘛包含函數傳回值的引用。檢測完傳回值以後記得釋放引用!

5.4. Extending Embedded Python

到目前為止,嵌套的Python解譯器還沒有訪問應用自身的功能。Python API通過擴充Python解譯器提供這種訪問。也就是說,嵌入的Python解譯器被應用提供的函數(routines)做了擴充。聽起來很複雜,其實也沒那麼誇張。暫時忘記應用啟動Python解譯器的事情。相反,把應用當成一組子程式,並寫了一些膠水代碼來使得Python可以訪問這些程式,就像寫一個普通Python擴充一樣。例如:

static int numargs=0;/* Return the number of arguments of the application command line */static PyObject*emb_numargs(PyObject *self, PyObject *args){    if(!PyArg_ParseTuple(args, ":numargs"))        return NULL;    return PyLong_FromLong(numargs);}static PyMethodDef EmbMethods[] = {    {"numargs", emb_numargs, METH_VARARGS,     "Return the number of arguments received by the process."},    {NULL, NULL, 0, NULL}};static PyModuleDef EmbModule = {    PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,    NULL, NULL, NULL, NULL};static PyObject*PyInit_emb(void){    return PyModule_Create(&EmbModule);}

把上述代碼插到main()函數之前。同時,把下面兩行代碼插到Py_Initialize()的調用之前。

numargs = argc;PyImport_AppendInittab("emb", &PyInit_emb);

這兩行初始化numargs變數,並使得emb.numargs()函數可以被內嵌的Python解譯器訪問。有了這些擴充,Python指令碼可以做下面這些事情了:

import embprint("Number of arguments", emb.numargs())

在一個實際的應用裡面,這些方法會暴露應用的一個API給Python.

5.5. Embedding Python in C++

把Python嵌入到C++程式中也是可以的;至於如何?就看系統C++的細節了;一般來講,我們需要用C++寫主程式並且用C++編譯器編譯和連結我們的程式。不需要用C++重新編譯Python.

5.6. Compiling and Linking under Unix-like systems

對於把Python解譯器嵌入到我們的應用裡面來,沒有必要專門為編譯器和連結器尋找正確的標記(find the right flags to pass to your compiler(and linker)),因為Python需要載入的庫模組是用C實現的動態擴充(C dynamic extensions).

為了找出需要的編譯器和連結器標記,我們可以執行PythonX.Y-config指令碼,這個指令碼是作為安裝過程的一部分而產生的(python3-config指令碼可能也是可用的)。這個指令碼有幾個選項,其中下面幾項的作用對我們來說將會更加直接:

pythonX.Y-config --cflags 為我們編譯提供推薦的標記:

$ /opt/bin/python3.3-config --cflags-I/opt/include/python3.3m -I/opt/include/python3.3m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes

pythonX.Y-config --ldflags 為我們連結提供推薦的標記:

$ /opt/bin/python3.3-config --ldflags-L/opt/lib/python3.3/config-3.3m -lpthread -ldl -lutil -lm -lpython3.3m -Xlinker -export-dynamic

注意:為了避免多個已安裝Python(尤其是系統Python和我們自己編譯的Python)之間的混亂(confusion),在上述例子中,我們使用pythonX.Y-config時應該使用絕對路徑。

如果這個程式不好使(不保證這個程式在所有類Unix(Unix-like)平台上都正常;然而我們歡迎彙報BUG(http://docs.python.org/3/bugs.html#reporting-bugs)),我們將不得不閱讀我們的系統關於動態連結的文檔和(或者)檢查Python的Makefile(用sysconfig.get_makefile_filename()(http://docs.python.org/3/library/sysconfig.html#sysconfig.get_makefile_filename)來找到它的位置)和編譯選項。在這種情況下,sysconfig(http://docs.python.org/3/library/sysconfig.html#module-sysconfig)模組是一個有用的工具,這個工具可以協助我們以編程的方式提取我們想要合并到一起的配置的值。例如:

>>> import sysconfig>>> sysconfig.get_config_var('LIBS')'-lpthread -ldl  -lutil'>>> sysconfig.get_config_var('LINKFORSHARED')'-Xlinker -export-dynamic'

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.