Обертывание библиотеки C в Python: C, Cython или ctypes?

Я хочу назвать библиотеку C из приложения Python. Я не хочу обертывать целый API, только функции и типы данных, которые относятся к моему случаю. Поскольку я вижу его, у меня есть три варианта:

  1. Создайте модуль фактического внутреннего абонента в C. Вероятно, излишество, и я также хотел бы избежать издержек изучения дополнительной записи.
  2. Используйте Cython для представления соответствующих частей от библиотеки C до Python.
  3. Сделайте все это в Python, с помощью ctypes общаться с внешней библиотекой.

Я не уверен или 2) или 3) являюсь лучшим выбором. Преимущество 3) является этим ctypes часть стандартной библиотеки, и получающимся кодом был бы чистый Python – хотя я не уверен, насколько большой, которые способствуют на самом деле.

Есть ли больше преимуществ / недостатки с любым выбором? Какой подход Вы рекомендуете?


Править: Спасибо за все Ваши ответы они предоставляют хороший ресурс любому надеющемуся делать что-то подобное. Решение, конечно, состоит в том, чтобы все еще быть принято для единственного случая — нет никого, "Это - правильная вещь" вид ответа. Для моего собственного случая я, вероятно, пойду с ctypes, но я также надеюсь испытать Cython в некотором другом проекте.

С тем, чтобы там быть никаким единственным истинным ответом принимая каждый несколько произволен; я выбрал ответ FogleBird's, поскольку он обеспечивает некоторое хорошее понимание ctypes, и это в настоящее время также - проголосовавший самым высоким образом ответ. Однако я предлагаю прочитать все ответы для получения хорошего обзора.

Еще раз спасибо.

271
задан balpha 26 December 2009 в 23:12
поделиться

6 ответов

ctypes - лучший вариант для быстрого выполнения задачи, и работать с ним приятно, пока вы все еще пишете Python!

Недавно я упаковал FTDI драйвер для связи с USB-чипом с использованием ctypes, и это было здорово. Я сделал все это менее чем за один рабочий день. (Я реализовал только те функции, которые нам были нужны, около 15 функций.)

Ранее мы использовали сторонний модуль, PyUSB , для той же цели. PyUSB - это фактический модуль расширения C / Python. Но PyUSB не выпускал GIL при блокировании чтения / записи, что вызывало у нас проблемы. Поэтому я написал наш собственный модуль, используя ctypes, который освобождает GIL при вызове собственных функций.

Следует отметить, что ctypes победили. я не знаю о константах #define и прочем в используемой вами библиотеке, только о функциях, поэтому вам придется переопределить эти константы в собственном коде.

Вот пример того, как код закончил поиск (много вырезано, просто пытаюсь показать вам суть):

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

Кто-то провел несколько тестов для различных вариантов.

Я мог бы быть более нерешительным, если бы мне пришлось заключить библиотека C ++ с множеством классов / шаблонов / и т. д. Но ctypes хорошо работает со структурами и может даже обратный вызов в Python.

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

Кто-то провел несколько тестов для различных опций.

Я мог бы быть более нерешительным, если бы мне пришлось обернуть библиотеку C ++ большим количеством классов / шаблонов / и т. Д. Но ctypes хорошо работает со структурами и может даже обратный вызов в Python.

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

Кто-то провел несколько тестов для различных опций.

Я мог бы быть более нерешительным, если бы мне пришлось обернуть библиотеку C ++ большим количеством классов / шаблонов / и т. Д. Но ctypes хорошо работает со структурами и может даже обратный вызов в Python.

111
ответ дан 23 November 2019 в 02:16
поделиться

Лично я бы написал модуль расширения на C. Не пугайтесь расширений Python C - их совсем несложно написать. Документация очень понятна и полезна. Когда я впервые написал расширение C на Python, я думаю, что мне потребовалось около часа, чтобы понять, как его написать - совсем немного времени.

18
ответ дан 23 November 2019 в 02:16
поделиться

Когда вызывающий и многопоточный метод совместно используют переменную, у вас уже есть доступ к ней - после завершения потока вы просто проверяете someValue .

Конечно , вы должны знать, когда будет завершен многопоточный метод, чтобы это было полезно. Внизу есть два способа сделать это:

  • Отправить обратный вызов в многопоточный метод, который он может выполнить по завершении. Вы можете передать свой метод обратного вызова someValue . Вы можете использовать эту технику, если вам все равно, когда выполняется обратный вызов.

  • Используйте WaitHandle какого-либо типа (или Thread.Join ). Они сообщают вам, когда ресурс готов или событие завершилось. Этот метод полезен, если вы хотите запустить поток, сделать что-нибудь еще, затем дождитесь завершения потока, прежде чем продолжить.

21
ответ дан 23 November 2019 в 02:16
поделиться

Cython - довольно крутой инструмент, который стоит изучить, и он удивительно близок к синтаксису Python. Если вы выполняете какие-либо научные вычисления с помощью Numpy, то Cython - это то, что вам нужно, потому что он интегрируется с Numpy для быстрых матричных операций.

Cython - это надмножество языка Python. Вы можете использовать любой действительный файл Python, и он выдаст корректную программу на C. В этом случае Cython просто сопоставляет вызовы Python с базовым API CPython. Это приводит к ускорению, возможно, на 50%, потому что ваш код больше не интерпретируется.

Чтобы получить некоторую оптимизацию, вы должны начать сообщать Cython дополнительные факты о вашем коде, такие как объявления типов. Если вы расскажете достаточно, это может свести код к чистому C. То есть цикл for в Python становится циклом for в C. Здесь вы увидите огромный прирост скорости. Здесь вы также можете ссылаться на внешние программы C.

Использовать код Cython также невероятно просто. Я думал, что в руководстве это звучит сложно. Вы буквально просто делаете:

$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so

, а затем можете импортировать mymodule в свой код Python и полностью забыть, что он компилируется до C.

В любом случае, потому что Cython так легко настроить и запустить Используя, я предлагаю попробовать его, чтобы узнать, подходит ли он вашим потребностям. Это не будет пустой тратой, если окажется, что это не тот инструмент, который вы ищете.

Я предлагаю попробовать, чтобы узнать, подходит ли он вашим потребностям. Это не будет пустой тратой, если окажется, что это не тот инструмент, который вы ищете.

Я предлагаю попробовать, чтобы узнать, подходит ли он вашим потребностям. Это не будет пустой тратой, если окажется, что это не тот инструмент, который вы ищете.

98
ответ дан 23 November 2019 в 02:16
поделиться

Если у вас уже есть библиотека с определенным API, я думаю, что ctypes - лучший вариант, так как вам нужно только немного инициализировать, а затем более или менее вызвать библиотеку так, как вы привыкли.

Я думаю, Cython или создание модуля расширения на C (что не очень сложно) более полезны, когда вам нужен новый код, например, вызов этой библиотеки и выполнение некоторых сложных, временных выполнение задач, а затем передача результата в Python.

Другой подход, для простых программ, заключается в непосредственном выполнении другого процесса (скомпилированном извне), вывод результата на стандартный вывод и вызов его с помощью модуля подпроцесса. Иногда это самый простой подход.

Например, если вы создаете консольную программу на C, которая работает более или менее таким же образом

$miCcode 10
Result: 12345678

Вы можете вызвать ее из Python

>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

С небольшим форматированием строки, Вы можете воспринимать результат как хотите. Вы также можете захватить стандартный вывод ошибок, так что он довольно гибкий.

9
ответ дан 23 November 2019 в 02:16
поделиться

ctypes - это здорово, когда у вас уже есть скомпилированный библиотечный блок (например, библиотеки OS). Однако, накладные расходы по вызову очень велики, так что если вы будете делать много вызовов в библиотеку, и вы все равно будете писать C-код (или, по крайней мере, компилировать его), то я бы сказал, что стоит попробовать cython. Это не так уж и много, и использовать полученный pyd-файл будет намного быстрее и более питоновым.

Я лично склонен использовать cython для быстрого ускорения питонового кода (циклы и сравнения целых чисел - это две области, где особенно сияет cython), и когда будет задействован еще какой-нибудь код/обертка других библиотек, я обращусь к Boost.Python. Boost.Python может быть привередливым в настройке, но как только он работает, он делает обёртывание Си/Си++ кода простым.

cython также хорош в обёртывании numpy (чему я научился в работе SciPy 2009), но я не использовал numpy, так что я не могу это прокомментировать.

10
ответ дан 23 November 2019 в 02:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: