通过 ctypes 从 Python 调用 C 代码,使用 python 对象列表
call C code from Python via ctypes, use python object list
我要
- 用c写一个列表求和的函数
sum_list
,命名为sum_list.c
- 将
sum_list.c
文件制作成sum_list.so
sum_list = ctypes.CDLL('./sum_list.so')
result = sum_list.sum_list([1, 2, 3])
它在第 4 步中引发错误:
ctypes.ArgumentError: argument 1: : Don't know how
to convert parameter 1
当我在 c 中编写一个将两个数字相加的函数时,它工作正常。
所以,关键是我不知道如何将列表(python 对象)传递给 c.
sum_list.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
long sum_list(PyObject *list)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PyList_Size(list);
if (n < 0)
return -1; /* Not a list */
for (i = 0; i < n; i++) {
item = PyList_GetItem(list, i); /* Can't fail */
if (!PyLong_Check(item)) continue; /* Skip non-integers */
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
return total;
}
python代码
from ctypes import CDLL
sum_list = CDLL('./sum_list.so')
l = [1, 2, 3]
sum_list.sum_list(l)
I expect the var
result in my python code is 6
.
ctypes 库用于调用纯 C 库中的函数。您找到的示例是一个函数,该函数将进入 Python 扩展模块,并且可以在没有 ctypes 的情况下使用。 Ctypes 实际上有一个 PyObject *
的类型,即 ctypes.py_object
。您需要将列表包装在其中 - 小心确保它仍然存在!
在任何情况下,您几乎都必须使用 restype
和 argtypes
.
提供正确的函数原型
这是一个工作示例:
import ctypes
sum_list = ctypes.PyDLL('./sum_list.so')
sum_list.restype = ctypes.c_long
sum_list.argtypes = [ ctypes.py_object ]
l = [1, 2, 3]
print(sum_list.sum_list(ctypes.py_object(l)))
注意,如 Mark 所注意到的,您必须在此处使用 PyDLL
,以确保 GIL 是 而不是 释放,因为函数没有获取它!
我已经 up-voted @Antii 的回答,但仍想添加一个简短、完整且 self-contained 的示例。
首先,ctypes
是与在没有考虑 Python 互操作性的情况下编写的 C 代码进行交互。通常,这意味着没有 #include <Python.h>
等的代码。例如:
/**
* Sample function to call from Python 3
* Build:
* $ gcc -shared -o libmath.so -fPIC math.c
*
* See
*
**/
int add(int x, int y) {
return x + y;
}
虽然上面的代码很简单,但您需要确保正确编译更复杂的代码片段。例如,如果您使用 C++ 编译器 and/or 正在使用特定于 C++ 的功能(例如 类、方法重载等),那么您 必须 在构建库时公开一个 C-compatible 接口,因为 C++ 的名称修改将使事后几乎不可能找到函数。
构建上面的 C 代码后,使用这个 Python 脚本调用它:
#!/usr/bin/env python3
import ctypes
cmath = ctypes.CDLL('./libmath.so')
cmath.restype = ctypes.c_int
cmath.argtypes = [ctypes.c_int, ctypes.c_int]
x = 4
y = 5
r = cmath.add(x, y)
print(f'{x} + {y} = {r}')
运行 来自我的终端的样本:
$ gcc -shared -o libmath.so -fPIC math.c
$ python3 math.py
4 + 5 = 9
正如另一个答案提到的,您可以使用 ctypes.py_object
来表示一个实际的 Python 对象,但您还必须记住,在调用 Python C API 时,您不能释放 GIL (全局解释器锁)。使用 PyDLL
而不是 CDLL
来完成此操作。
这是一个适用于一般序列的完整示例:
test.c
#include <Python.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
define API
#endif
API long sum_list(PyObject* o)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject* list;
PyObject* item;
list = PySequence_Fast(o,"not a sequence"); // New reference! Must DECREF eventually.
if(list == NULL)
return -1;
n = PySequence_Fast_GET_SIZE(list);
for (i = 0; i < PySequence_Fast_GET_SIZE(list); i++) {
item = PySequence_Fast_GET_ITEM(list, i);
if (!PyLong_Check(item))
{
PyErr_SetString(PyExc_TypeError,"sequence contains non-integer");
Py_DECREF(list);
return -1;
}
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
{
Py_DECREF(list);
return -1;
}
total += value;
}
Py_DECREF(list);
return total;
}
test.py
from ctypes import *
dll = PyDLL('test')
dll.sum_list.restype = c_long
dll.sum_list.argtypes = py_object,
print(dll.sum_list((1,2,3,4,5)))
try:
print(dll.sum_list(7))
except TypeError as e:
print(e)
try:
print(dll.sum_list((1,2,'a',4,5)))
except TypeError as e:
print(e)
try:
print(dll.sum_list((1,2,0x80000000,4,5)))
except OverflowError as e:
print(e)
try:
print(dll.sum_list((1,2,-0x80000001,4,5)))
except OverflowError as e:
print(e)
输出:
15
not a sequence
sequence contains non-integer
Python int too large to convert to C long
Python int too large to convert to C long
我要
- 用c写一个列表求和的函数
sum_list
,命名为sum_list.c - 将
sum_list.c
文件制作成sum_list.so sum_list = ctypes.CDLL('./sum_list.so')
result = sum_list.sum_list([1, 2, 3])
它在第 4 步中引发错误:
ctypes.ArgumentError: argument 1: : Don't know how to convert parameter 1
当我在 c 中编写一个将两个数字相加的函数时,它工作正常。
所以,关键是我不知道如何将列表(python 对象)传递给 c.
sum_list.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
long sum_list(PyObject *list)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PyList_Size(list);
if (n < 0)
return -1; /* Not a list */
for (i = 0; i < n; i++) {
item = PyList_GetItem(list, i); /* Can't fail */
if (!PyLong_Check(item)) continue; /* Skip non-integers */
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
return total;
}
python代码
from ctypes import CDLL
sum_list = CDLL('./sum_list.so')
l = [1, 2, 3]
sum_list.sum_list(l)
I expect the
var
result in my python code is6
.
ctypes 库用于调用纯 C 库中的函数。您找到的示例是一个函数,该函数将进入 Python 扩展模块,并且可以在没有 ctypes 的情况下使用。 Ctypes 实际上有一个 PyObject *
的类型,即 ctypes.py_object
。您需要将列表包装在其中 - 小心确保它仍然存在!
在任何情况下,您几乎都必须使用 restype
和 argtypes
.
这是一个工作示例:
import ctypes
sum_list = ctypes.PyDLL('./sum_list.so')
sum_list.restype = ctypes.c_long
sum_list.argtypes = [ ctypes.py_object ]
l = [1, 2, 3]
print(sum_list.sum_list(ctypes.py_object(l)))
注意,如 Mark 所注意到的,您必须在此处使用 PyDLL
,以确保 GIL 是 而不是 释放,因为函数没有获取它!
我已经 up-voted @Antii 的回答,但仍想添加一个简短、完整且 self-contained 的示例。
首先,ctypes
是与在没有考虑 Python 互操作性的情况下编写的 C 代码进行交互。通常,这意味着没有 #include <Python.h>
等的代码。例如:
/**
* Sample function to call from Python 3
* Build:
* $ gcc -shared -o libmath.so -fPIC math.c
*
* See
*
**/
int add(int x, int y) {
return x + y;
}
虽然上面的代码很简单,但您需要确保正确编译更复杂的代码片段。例如,如果您使用 C++ 编译器 and/or 正在使用特定于 C++ 的功能(例如 类、方法重载等),那么您 必须 在构建库时公开一个 C-compatible 接口,因为 C++ 的名称修改将使事后几乎不可能找到函数。
构建上面的 C 代码后,使用这个 Python 脚本调用它:
#!/usr/bin/env python3
import ctypes
cmath = ctypes.CDLL('./libmath.so')
cmath.restype = ctypes.c_int
cmath.argtypes = [ctypes.c_int, ctypes.c_int]
x = 4
y = 5
r = cmath.add(x, y)
print(f'{x} + {y} = {r}')
运行 来自我的终端的样本:
$ gcc -shared -o libmath.so -fPIC math.c
$ python3 math.py
4 + 5 = 9
正如另一个答案提到的,您可以使用 ctypes.py_object
来表示一个实际的 Python 对象,但您还必须记住,在调用 Python C API 时,您不能释放 GIL (全局解释器锁)。使用 PyDLL
而不是 CDLL
来完成此操作。
这是一个适用于一般序列的完整示例:
test.c
#include <Python.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
define API
#endif
API long sum_list(PyObject* o)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject* list;
PyObject* item;
list = PySequence_Fast(o,"not a sequence"); // New reference! Must DECREF eventually.
if(list == NULL)
return -1;
n = PySequence_Fast_GET_SIZE(list);
for (i = 0; i < PySequence_Fast_GET_SIZE(list); i++) {
item = PySequence_Fast_GET_ITEM(list, i);
if (!PyLong_Check(item))
{
PyErr_SetString(PyExc_TypeError,"sequence contains non-integer");
Py_DECREF(list);
return -1;
}
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
{
Py_DECREF(list);
return -1;
}
total += value;
}
Py_DECREF(list);
return total;
}
test.py
from ctypes import *
dll = PyDLL('test')
dll.sum_list.restype = c_long
dll.sum_list.argtypes = py_object,
print(dll.sum_list((1,2,3,4,5)))
try:
print(dll.sum_list(7))
except TypeError as e:
print(e)
try:
print(dll.sum_list((1,2,'a',4,5)))
except TypeError as e:
print(e)
try:
print(dll.sum_list((1,2,0x80000000,4,5)))
except OverflowError as e:
print(e)
try:
print(dll.sum_list((1,2,-0x80000001,4,5)))
except OverflowError as e:
print(e)
输出:
15
not a sequence
sequence contains non-integer
Python int too large to convert to C long
Python int too large to convert to C long