通过 ctypes 从 Python 调用 C 代码,使用 python 对象列表

call C code from Python via ctypes, use python object list

我要

  1. 用c写一个列表求和的函数sum_list,命名为sum_list.c
  2. sum_list.c文件制作成sum_list.so
  3. sum_list = ctypes.CDLL('./sum_list.so')
  4. 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。您需要将列表包装在其中 - 小心确保它仍然存在!

在任何情况下,您几乎都必须使用 restypeargtypes.

提供正确的函数原型

这是一个工作示例:

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