Python C API 使用心得
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
Python C API 使用心得
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
工作的變化簡直和人生變化一樣不可預知.
想8月份看完《Programming in LUA》以後,對指令碼語言重拾興趣,後來斷斷續續也算把《Python核心程式設計》,這本來是非常純粹的Just for fun的事情,最後確還是免不了染上工作的性質,呵呵,這樣說好像很奇怪,應該是說,憑藉著一點興趣,花了很多業餘時間去學習的Python, 想不到竟然能這麼快就在工作中有所應用與發揮,實話說,做老闆的,肯定希望有這樣的員工。。。。。何況,在老闆心中我還算是新員工呢。我要是老闆,我也希望做一個新東西前,本來說要調研調研的。。。。結果已經有員工用業餘時間已經做過了,然後可以直接開工,唉。。。。我不知道碰到這樣的情況我為什麼會嘆 氣。。。。。。。
其實以前學的主要是Python語言的語法,用Python C API來寫擴充套件模組還真沒有寫過,要是用LUA的話,雖然特別複雜,但是可能好歹還有個底。最後幾乎是純粹靠文件來解決了一切,(還有老總提供的一些沒頭沒尾的例子)不要問我為什麼不上網查,資料太少,而且在公司上網並不方便。
這裡總結一下,以防忘記(我並不準備詳細記錄下每個步驟,僅僅記要點,假如需要詳細步驟的,請參看Python document中的例子)
一:用C API為Python寫C語言函式,以方便Python中呼叫,這種方法還是很簡單的,和LUA C API的方法基本一樣。(參看文件的Extending Python with C or C++
1. 首先實現一個特定原型的函式,用Python C API來實現的話,所有函式必須是這種原型。必須是類似這樣的
PyObject *Fun(PyObject *self, PyObject *args)
self應該是在用類的時候才會用到(我沒有用到),args就是函式的引數。因為args是一個PyObject*型別(可以代表Python語言中的任何型別)
2. 將引數轉換成C 語言表示的內容,用PyArg_ParseTuple函式。
3. 執行完需要的操作後,也必須返回一個PyObject*型別的值。通過Py_BuildValue函式來構建。
這裡要說的是,假如希望返回一個Tuple型別的值,可以先用
PyObject *tuple = Py_BuildValue("(iis)", 1, 2, "three");
形式來構建,假如很多的話,可以用下面的方式來構建
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyString_FromString("three"));
這一點在剛開始開工的時候迷惑了很久。
4. 將要輸出的所有函式放入一個數組中,陣列的結構是:
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
陣列以{NULL, NULL}結束
5. 構造一個Python import時初始化的函式
類似
PyMODINIT_FUNC
initexample(void)
{
Py_InitModule("example", example_methods);
}
這裡有個特別需要注意的是,初始化函式名字有嚴格要求,init後面必須跟模組名,否則Python找不到確定的函式會報沒有初始化函式的錯誤
總結一下,記得第一次學習LUA C API介面的時候,碰到類似的擴充套件模組寫法,還是感覺很奇怪,“怎麼搞這麼複雜啊”。。。。。當時為了實現一個簡單的擴充套件都是折騰了很久,到了再一次碰到類似的問題,(Python C API寫擴充套件的時候幾乎就是和LUA C API一樣),基本已經輕車熟路了。這裡突然也想說一句,就算不是真的寫非常底層的介面,但是用的介面多了,都是能有些感覺的。
擴充套件模組寫完後,編譯成動態庫(Python要求此動態庫名字為pyd,實際就是改個字尾而已)。就可以直接在Python指令碼中用import的方式載入了,對於使用來說,根本不需要知道此庫是用C API擴充套件寫的還是直接用Python語句寫的(這點Lua做的也是一樣好)
最後,python的原始碼中附帶了一個叫做example_nt的例子,可以參考一樣,完整的擴充套件程式碼如下:
#include "Python.h"
static PyObject *
ex_foo(PyObject *self, PyObject *args)
{
printf("Hello, world/n");
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef example_methods[] = {
{"foo", ex_foo, METH_VARARGS, "foo() doc string"},
{NULL, NULL}
};
PyMODINIT_FUNC
initexample(void)
{
Py_InitModule("example", example_methods);
}
二.C語言中呼叫Python語句
首先,void Py_Initialize()用來初始化,void Py_Finalize()用來結束Python的呼叫,這是必須要的。
燃火分兩種情況,假如僅僅是幾條語句的話,那麼以PyRun_為字首的一些函式都很好用,比如
int PyRun_SimpleString(const char *command)
函式就可以直接執行一條char*的Python語句。
需要獲得返回值得話
PyObject* PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals)
也很好用,以上兩個函式用來處理Python原始碼已經讀入記憶體的情況,在檔案中的時候
int PyRun_SimpleFile(FILE *fp, const char *filename)
PyObject* PyRun_File(FILE *fp, const char *filename, int start, PyObject *globals, PyObject *locals)
使用類似。不多講了。
假如是個模組的話(比如一個函式),希望在C語言中呼叫的話那麼使用起來就稍微複雜了一點。這種情況的需要在於你可以從C語言中向Python函式中傳入引數並且執行,然後獲取結果。
此處又分為幾種情況:
在檔案中,在記憶體中,編譯過的,原始碼。
在檔案中都很好解決,和上面一樣。這裡主要講在記憶體中的情況。(事實上我工作中需要並且耗費了很長時間才找到解決方法的就是這種情況)
未編譯時:(也就是原始碼)
1.通過
PyObject* Py_CompileString(const char *str, const char *filename, int start)
API首先編譯一次。此API的引數我說明一下,str就是記憶體中的原始碼,filename主要是出錯時報錯誤用的,事實測試證明,你隨意給個字串也沒有關係,但給NULL引數在執行時必然報錯。start我一般用的是Py_file_input,因為的確是從檔案中讀取過來的,相對的還有Py_single_input用來表示一條語句,Py_eval_input的用法我也不是太清楚。
原始碼通過此函式呼叫後,獲得編譯後的PyObject*,(其實假如跟進原始碼中去看,是一個PyCodeObject結構)假設命名為lpCode。
2.此時再呼叫API
PyObject* PyImport_ExecCodeModule(char *name, PyObject *co)
匯入模組。引數也說明一下,name為匯入的模組名,co就是前面編譯過的程式碼物件(lpCode)。返回的就是模組物件了,假設命名為lpMod。
3.再呼叫API
PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)
獲得函式物件。o就是模組物件(lpMod),attr_name就是你想要呼叫的函式名了,假設叫main的函式,就是”main”,然後返回的就是函式物件,假設命名為lpFun。
4.此時可以用API
int PyCallable_Check(PyObject *o)
去檢查一下是不是獲得了一個函式。假如確定的話,就可以直接用
PyObject_Call開頭的一族函式呼叫lpFun了。這些函式包括很多,一般就是輸入引數的不同,但是效果都是一樣的,就是呼叫函式而已。引數一般可以通過前面說過的build函式來獲得,返回值也是獲得一個PyObject*,可以通過PyArg_那個函式來獲取,但是好像不太好,那是分析引數用的。推薦用確定型別(假設為type)的類似Py[type]_As的函式來獲取。
比如:
long PyLong_AsLong(PyObject *pylong)獲取long
double PyLong_AsDouble(PyObject *pylong)獲取double
這裡想說的是,應該有直接從原始碼中獲取函式呼叫物件的方式,但是我本人沒有試出來,有人知道請一定賜教!
編譯過的程式碼:
對於編譯過的程式碼和上面就是獲得編譯後的PyCodeObject物件,當然在原始碼中表示還是PyObject*的方法不同(上例中的lpCode)。
當然要想以後獲得一個編譯後的lpCode,自然要先編譯一下啦。但是純粹編譯成pyc結尾的檔案後,直接讀入記憶體,我沒有找到將其轉化為PyCodeObject物件的方法(也希望有人知道能告訴我!)
我找到的方法是先用
PyObject* PyMarshal_WriteObjectToString(PyObject *value, int version)
void PyMarshal_WriteLongToFile(long value, FILE *file, int version)
兩個函式先把PyCodeObject物件(lpCode)序列化到檔案或者記憶體中。
再在需要的時候用函式
PyObject* PyMarshal_ReadObjectFromFile(FILE *file)
PyObject* PyMarshal_ReadObjectFromString(char *string, Py_ssize_t len)
讀出來,讀出來的PyObject*其實就是想要的PyCodeObject物件了(lpCode)。接下來的步驟與未編譯時的步驟一樣。
光是這個扭曲的方法我還是參考老總給的半邊資料反覆研究出來的。而真正直接有效的方法我還是沒有找到。
我希望能解決的問題我總結一下,有2個
1. 直接從原始碼中讀入,轉成模組物件(lpMod),而不需要先通過Py_CompileString的呼叫。
2. 直接從pyc檔案(記憶體)中讀入,轉成程式碼物件(lpCode)或者模組物件(lpMod),扭曲的通過序列化PyCodeObject物件再序列化出來的方式。
希望我從文件中摸索出來的東西對於有些同樣需要的人有價值。這一些方法我在網上也找到,但是沒有找到,最後還是靠自己摸索才找出了一些扭曲的方式來解決問題。
這裡想對那些動不動就說你想把文件都看看的,你先把什麼都看仔細了的兄弟們說一下,並不是每個老闆都允許員工幹一件事情前去把一種新的語言全學精了,哪怕他明知道這就是一種你不知道的語言。有的時候,just for work而 已。當然,在有時間的時候,自然我也是想把該學的都好好學了,只是,工作的時候,身不由己啊,何況,該學的東西是很多的,個人也可能有個人的學習計劃,並不是工作中碰到一種問題,我的業餘時間也得全部搭上去,把這件事情學通,所以請多給一些諒解,碰到有人問對你來說很初級的問題時,你好心的就給個回答,我 很感謝,不想回答的起碼也不要說風涼話,我也很感謝了。其實我倒是也沒有真發帖去問,因為等到有人回答估計一天的工作時間都耽誤了,只是看到有類似的情況,所以隨便說說,希望不要見怪。
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie