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

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.py
def 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]$ ./py
hello,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>

int
main(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>

int
main(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 2
Will compute 3 times 2
Result 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 emb
print("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'

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章