怎麼給 Python 寫 C 擴展?
作者:nucpylab
原文:http://www.cnblogs.com/nucpylab/p/8608722.html
1. 環境準備
https://mp.weixin.qq.com/s/S2B85S8Yw5zcZgeqp-MqDA
如果是Linux只需要安裝Python3.x + Python-dev。
Windows下稍微複雜點,VS2017 + Python3.6.3
VS2017可用社區版,需要選擇安裝的環境如下:
2. Hello World !
2.1 C模塊封裝
以計算兩個數相加為例,選擇任意文件夾,新建如下C語言源碼:
// 文件名 calc.c
#include
int
add
(
int
x
,
int
y
){
// C 函數
return
x
+
y
;
}
static
PyObject
*
calc_add
(
PyObject
*
self
,
PyObject
*
args
){
int
x
,
y
;
// Python傳入參數
// "ii" 表示傳入參數為2個int型參數,將其解析到x, y變數中
if
(!
PyArg_ParseTuple
(
args
,
"ii"
,
&
x
,
&
y
))
return
NULL
;
return
PyLong_FromLong
(
add
(
x
,
y
));
}
// 模塊的方法列表
static
PyMethodDef
CalcMethods
[]
=
{
{
"add"
,
calc_add
,
METH_VARARGS
,
"函數描述"
},
{
NULL
,
NULL
,
0
,
NULL
}
};
// 模塊
static
struct
PyModuleDef
calcmodule
=
{
PyModuleDef_HEAD_INIT
,
"calc"
,
// 模塊名
NULL
,
// 模塊文檔
-
1
,
/* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
CalcMethods
};
// 初始化
PyMODINIT_FUNC
PyInit_calc
(
void
)
{
return
PyModule_Create
(&
calcmodule
);
}
其中,
靜態函數
calc_add 以python的C介面方式封裝了add函數,命名方式模塊名
_
函數名
靜態PyMethodDef列表
變數CalcMethods
包含了該模塊方法的描述靜態struct PyModuleDef結構體
變數calcmodule
定義了模塊的描述PyInit_calc
函數初始化了模塊,命名方式PyInit_
模塊名
2.2 C源碼編譯
在VS2017中可以直接生成 .dll 文件,然後改名為 .pyd 就可在python程序中引入該模塊了,但是,這不"清真",正確的姿勢是寫一個 setup . py
然後通過python調cl.exe編譯。
新建 setup . py
文件,內容如下:
# setup.py
from
distutils
.
core
import
setup
,
Extension
module1
=
Extension
(
"calc"
,
sources
=[
"calc.c"
])
setup
(
name
=
"calc_model"
,
version
=
"1.0"
,
description
=
"Hello ?"
,
ext_modules
=[
module1
]
)
然後,從Windows的命令行(命令提示符)下進入到這個文件夾下,執行:
python setup . py build
即可完成編譯,如果出現某 . bat
文件未找到,說明你的VS沒有安裝相應的依賴(Linux下編譯不成功原因可能是沒有裝python-dev),按文章開頭給出的依賴庫添加修改(此時不需要重新安裝VS)。
編譯結束後,在該文件夾下會出現
build
文件夾,進入該文件夾,出現如下兩個文件夾:進入
lib.xxx
那個文件夾,裡面有個.pyd
結尾的文件(Linux下為.so
結尾),這就是我們編譯好的python模塊了,如下:當然,你也可以改名為
calc.pyd
比較好看,不過這不影響調用。2.3 Python調用
這部分就簡單了,進入含有編譯好的
.pyd
文件夾,新建如下文件:import
calc
print
(
calc
.
add
(
12
,
21
))
這就是一個普通庫,這樣調用就OK了。
3. Python的參數傳遞以及C的返回值相關問題
這部分我直接甩出文件就行,編譯及調用過程與上面一樣。
C 文件
/**構建返回值
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") "hello"
Py_BuildValue("y", "hello") b"hello"
Py_BuildValue("ss", "hello", "world") ("hello", "world")
Py_BuildValue("s#", "hello", 4) "hell"
Py_BuildValue("y#", "hello", 4) b"hell"
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456) {"abc": 123, "def": 456}
Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
**/
#include
static
PyObject
*
value_commonArgs
(
PyObject
*
self
,
PyObject
*
args
){
// 傳入普通參數,例如: s = value.com(1, 2.3, "Hello C")
int
x
;
double
y
;
char
*
z
;
if
(!
PyArg_ParseTuple
(
args
,
"ids"
,
&
x
,
&
y
,
&
z
))
return
NULL
;
printf
(
"The args is %d and %f and %s .n"
,
x
,
y
,
z
);
// 返回(x, y, z)的元組
return
Py_BuildValue
(
"(i,d,s)"
,
x
,
y
,
z
);
}
static
PyObject
*
value_tupleTest
(
PyObject
*
self
,
PyObject
*
args
){
// t = value.tut((1, 3), "Tuple")
int
x
,
y
;
char
*
z
;
if
(!
PyArg_ParseTuple
(
args
,
"(ii)s"
,
&
x
,
&
y
,
&
z
))
return
NULL
;
printf
(
"The args is (%d, %d), %s .n"
,
x
,
y
,
z
);
// return ([1, 2], "hello")
return
Py_BuildValue
(
"[i,i]s"
,
x
,
y
,
z
);
}
static
PyObject
*
value_some
(
PyObject
*
self
,
PyObject
*
args
){
/* 可選參數,可能是下面幾種, "|" 代表後面的參數可選
c = value.som(1)
value.som(1, 3)
value.som(1, 2, "hello")
*/
int
x
=
0
,
y
=
0
;
char
*
z
=
NULL
;
if
(!
PyArg_ParseTuple
(
args
,
"i|is"
,
&
x
,
&
y
,
&
z
))
return
NULL
;
printf
(
"x is: %dn"
,
x
);
printf
(
"y is: %dn"
,
y
);
if
(
z
!=
NULL
)
printf
(
"z is: %sn"
,
z
);
return
TAG:Python開發 |