вызывать код C из Python через ctypes, использовать список объектов python - PullRequest
3 голосов
/ 06 июня 2019

Я хочу

  1. написать функцию sum_list для суммирования списка в c с именем 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: аргумент 1:: Донне знаю, как преобразовать параметр 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;
}

код питона

from ctypes import CDLL

sum_list = CDLL('./sum_list.so')

l = [1, 2, 3]
sum_list.sum_list(l)

Я ожидаю, что var результат в моем коде Python будет 6.

Ответы [ 3 ]

5 голосов
/ 06 июня 2019

Библиотека ctypes предназначена для вызова функций в чистых библиотеках Си.Пример, который вы нашли, относится к функции, которая входит в модуль расширения 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)))

Примечание , как заметил Марк, вместо этого вы должны использовать PyDLL, чтобыGIL не освобожден, так как функция не получает его!

3 голосов
/ 06 июня 2019

Как уже упоминалось в другом ответе, вы можете использовать ctypes.py_object для представления фактического объекта Python, но вы также должны помнить, что при вызове API-интерфейсов Python C нельзя снимать 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
3 голосов
/ 06 июня 2019

Я проголосовал за ответ @ Antii, но все же хотел добавить короткий, полный и самостоятельный пример.

Во-первых, ctypes - это взаимодействие с кодом C, который был написан без учета возможности взаимодействия с Python. Как правило, это означает, что код не #include <Python.h> и так далее. Например:

/**
 * Sample function to call from Python 3
 * Build:
 *  $ gcc -shared -o libmath.so -fPIC math.c
 *
 * See https://stackoverflow.com/a/14884166/4594973
 *
 **/
int add(int x, int y) {
    return x + y;
}

Хотя приведенный выше код является тривиальным, вам необходимо убедиться, что более сложные фрагменты кода правильно скомпилированы. Например, если вы используете компилятор C ++ и / или используете функции, специфичные для C ++ (например, классы, перегрузка методов и т. Д.), То вы должны предоставить C-совместимый интерфейс при создании библиотеки, так как искажение имен в 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
...