1. 程式人生 > >擴充套件Python之在Python中呼叫C編寫的函式模組

擴充套件Python之在Python中呼叫C編寫的函式模組

目錄

編寫Python擴充套件

1. 建立應用程式碼

  • 編寫一個比較簡單的函式,求階乘。並且寫上main函式進行測試。
    在這裡插入圖片描述

  • 編譯測試

$ gcc Extest1.c -o Extest
$ ./Extest
4! = 24

2. 根據樣板編寫封裝程式碼

2.1 包含Python標頭檔案

  • 在大多數類UNIX系統上,Python包含檔案一般位於/usr/local/include/python2.x 或者 /usr/include/python2.x 中。
  • 我的系統是ubuntu 18.04 LTS。沒有python2,系統自動安裝的只有python3。我的位於 /usr/include/python3.6
    在這裡插入圖片描述
  • Python.h
    這個標頭檔案包含在原始碼中
#include "Python.h"
  • 這邊之後編譯這個檔案時可能會出現錯誤 fatal error: Python.h: No such file or directory compilation terminated.
    stack overflow上的解決方案
sudo apt-get install python3-dev

2.2 為每一個模組函式新增形如PyObject* Module_func() 的封裝函式

  • 對於每個需要在Python環境中訪問的函式,需要建立一個以static PyObjuec* 標識,以模組名開頭,緊接著是下劃線和函式名本身的函式。
  • 例如,若要讓 fac() 函式可以在Python中匯入,並將Extest 作為最終的模組名稱,需要建立一個名為Extest_fac 的封裝函式。在用到這個函式的Python指令碼中,可以使用import ExtestExtest.fac() 的形式在任意地方呼叫fac() 函式。
  • 封裝函式的任務是將Python中的值轉成C形式,接著呼叫相應的函式。當C函式執行完畢時,需要返回Python的環境中。封裝函式需要將返回值轉換成Python形式,並進行真正的返回,傳回所有需要的值。
    在這裡插入圖片描述

2.3 為每一個模組函式新增一個PyMethodDef ModuleMethods[] 陣列/表

在這裡插入圖片描述

  • 這邊書裡《Python核心程式設計》和文件稍微有點出入,書裡是3個引數,文件是4個引數,但是文件沒有寫第4個引數是什麼,所以這邊先按書裡寫的。

2.4 新增模組初始化函式

  • 書裡是這麼寫的,我懷疑是python2和3的區別,所以這裡書裡的不太對,我這邊在後面的執行中會出現問題,所以這裡稍微改一下。用文件的內容。
    在這裡插入圖片描述
  • 文件裡的初始化格式:
    在這裡插入圖片描述

3 編譯並測試

3.1 建立 setup.py

在這裡插入圖片描述

3.2 執行 setup.py 來編譯並連結程式碼

  • 執行該命令構建擴充套件
python3 setup.py build

在這裡插入圖片描述

  • Error 1: 找不到python.h。解決方案見上
  • Error 2: 用書裡的函式會顯示沒有initModule這個函式。解決方案見上
  • Error 3: 顯示找不到distutils 模組No module named 'distutils.core'解決方案
sudo apt-get install python3-distutils

3.3 在Python中匯入模組

  • 擴充套件模組會建立在build/lib.*目錄下,即執行setup.py指令碼的位置。要麼切換到這麼目錄中,要麼用下面的方式將氣安裝到Python中。
$ python3 setup.py install

在這裡插入圖片描述

  • Error 這裡有了問題,注意最後一行Permission denied ,所以這裡用 sudo python3 setup.py install 就可以了。

3.4 測試函式

在這裡插入圖片描述

4. 文件擴充套件

4.1 在2.2中出現的 int PyArg_ParseTuple(PyObject *arg, const char *format, ...); 函式

  • 函式文件 1.7
  • fac()的示例中,當客戶程式呼叫Extest.fac()時,會呼叫封裝函式。這裡會接受一個Python整數,將其轉換成C整數,接著呼叫C函式fac(),獲取返回結果,同樣是一個整數。將這個返回值轉換成Python整數,返回給呼叫者(記住,編寫的封裝函式就是def fac(n)宣告的代理函式。當這個封裝函式返回時,就相當於Python fac()函式執行完畢了)。
  • 如何完成轉換?Python -> C : PyArg_Parse*(). C->Python : Py_BuildValue()
  • PyArg_Parse*() : 類似於C中的sscanf 函式。接受一個位元組流,然後根據一些格式字串進行解析,將結果放入到相應指標所指的變數中。若解析成功就返回1;否則返回0.
  • Py_BuildValue() :類似於 sprintf 函式,接受一個格式字串,並將所有引數按照格式字串指定的格式轉換成一個Python 物件。

Python和C/C++之間的“轉換編碼”

格式編碼 Python資料型別 C/C++資料型別
s, s# str/unicode, len() char*, int
i int int
b int char
c str char
d float double
h int short
l int long

取自文件的例子:文件1.7部分

#include "Python.h"
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;

ok = PyArg_ParseTuple(args, ""); /* No arguments */
    /* Python call: f() */

ok = PyArg_ParseTuple(args, "s", &s); /* A string */
    /* Possible Python call: f('whoops!') */

ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
    /* Possible Python call: f(1, 2, 'three') */

ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* A pair of ints and a string, whose size is also returned */
    /* Possible Python call: f((1, 2), 'three') */

{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* A string, and optionally another string and an integer */
    /* Possible Python calls:
       f('spam')
       f('spam', 'w')
       f('spam', 'wb', 100000) */
}

{
    int left, top, right, bottom, h, v;
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
             &left, &top, &right, &bottom, &h, &v);
    /* A rectangle and a point */
    /* Possible Python call:
       f(((0, 0), (400, 300)), (10, 10)) */
}

4.2 在3.1中建立的setup.py

參考書籍:
《Python核心程式設計》Unit8
百度網盤連結:https://pan.baidu.com/s/1XYSY65_BuQkEHm-LFzzi3g 提取碼:awqc
《Python3文件》https://docs.python.org/3/ 這篇文章主要需要用到的文件內容上面已經給了