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

StackOverflow https://stackoverflow.com/questions/1942298

  •  20-09-2019
  •  | 
  •  

Вопрос

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

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

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

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


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

Поскольку единственного истинного ответа не существует, принятие одного из них является несколько произвольным;Я выбрал ответ FogleBird, поскольку он дает хорошее представление о ctypes, и в настоящее время он также является ответом с наибольшим количеством голосов.Тем не менее, я предлагаю прочитать все ответы, чтобы получить хорошее представление.

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

Это было полезно?

Решение

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

Недавно я завернул ФТДИ драйвер для связи с 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.

Другие советы

Предупреждение:мнение разработчика ядра Cython впереди.

Я почти всегда рекомендую Cython вместо ctypes.Причина в том, что путь обновления гораздо более плавный.Если вы используете ctypes, многие вещи поначалу будут простыми, и, конечно, здорово писать свой FFI-код на простом Python, без компиляции, сборки зависимостей и всего такого.Однако в какой-то момент вы почти наверняка обнаружите, что вам приходится часто обращаться к вашей библиотеке C, либо в цикле, либо в виде более длинной серии взаимозависимых вызовов, и вам захочется ускорить этот процесс.В этот момент вы заметите, что с ctypes этого сделать нельзя.Или, когда вам нужны функции обратного вызова, и вы обнаружите, что ваш код обратного вызова Python становится узким местом, вы хотели бы ускорить его и/или переместить его также в C.Опять же, вы не можете сделать это с ctypes.Таким образом, вам придется сменить язык в этот момент и начать переписывать части вашего кода, потенциально перепроектируя ваш код Python/ctypes в простой C, тем самым лишая себя всей выгоды от написания вашего кода на простом Python.

С Cython, OTOH вы совершенно свободны делать код оболочки и вызова настолько тонким или толстым, насколько захотите.Вы можете начать с простых вызовов кода C из обычного кода Python, а Cython преобразует их в собственные вызовы C без каких-либо дополнительных затрат на вызовы и с чрезвычайно низкими затратами на преобразование параметров Python.Когда вы заметите, что вам нужно еще больше производительности в какой-то момент, когда вы делаете слишком много дорогостоящих вызовов в вашей библиотеке C, вы можете начать аннотировать окружающий код Python статическими типами и позволить Cython оптимизировать его прямо для вас до C.Или вы можете начать переписывать части своего кода C на Cython, чтобы избежать вызовов, а также алгоритмически специализировать и ужесточить циклы.А если вам нужен быстрый обратный вызов, просто напишите функцию с соответствующей сигнатурой и передайте ее напрямую в реестр обратного вызова C.Опять же, никаких накладных расходов, и это дает вам простую производительность вызовов C.И в гораздо менее вероятном случае, когда вы действительно не можете достаточно быстро получить свой код на Cython, вы все равно можете рассмотреть возможность переписывания действительно важных его частей на C (или C ++ или Fortran) и вызывать его из вашего кода Cython естественным и естественным образом.Но тогда это действительно становится последним средством, а не единственным вариантом.

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

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

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

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

Для вызова библиотеки C из приложения Python также существует cffi что является новой альтернативой для cтипы.Это привносит новый взгляд на FFI:

  • он решает проблему увлекательным и чистым способом (в отличие от cтипы)
  • не требуется писать код, отличный от Python (как в СВИГ, Китон, ...)

Я выложу еще один: СВИГ

Его легко освоить, он многое делает правильно и поддерживает множество языков, поэтому время, потраченное на его изучение, может быть весьма полезным.

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

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

cтипы отлично подходит, когда у вас уже есть скомпилированный библиотечный объект (например, библиотеки ОС).Однако накладные расходы на вызовы являются серьезными, поэтому, если вы будете делать много вызовов в библиотеку и все равно собираетесь писать код C (или, по крайней мере, компилировать его), я бы посоветовал пойти на цитон.Это не так уж и много работы, и использовать полученный файл pyd будет намного быстрее и более питонично.

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

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

Я думаю, если у вас уже есть библиотека с определенным 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

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

Есть одна проблема, из-за которой я использовал ctypes, а не cython, и которая не упоминается в других ответах.

При использовании ctypes результат вообще не зависит от используемого вами компилятора.Вы можете написать библиотеку, используя более или менее любой язык, который можно скомпилировать в собственную общую библиотеку.Не имеет большого значения, какая система, какой язык и какой компилятор.Однако Cython ограничен инфраструктурой.Например, если вы хотите использовать компилятор Intel в Windows, заставить cython работать гораздо сложнее:вам следует «объяснить» компилятор cython, перекомпилировать что-нибудь с помощью именно этого компилятора и т. д.Что существенно ограничивает портативность.

Если вы ориентируетесь на Windows и решили использовать некоторые проприетарные библиотеки C++, то вскоре вы можете обнаружить, что разные версии msvcrt***.dll (Visual C++ Runtime) немного несовместимы.

Это означает, что вы не сможете использовать Cython с момента получения wrapper.pyd связано с msvcr90.dll (Питон 2.7) или msvcr100.dll (Питон 3.x).Если библиотека, которую вы создаете, связана с другой версией среды выполнения, вам не повезло.

Затем, чтобы все заработало, вам нужно будет создать оболочки C для библиотек C++, связать эту dll-оболочку с той же версией msvcrt***.dll в качестве вашей библиотеки C++.А затем используйте ctypes для динамической загрузки созданной вручную dll-оболочки во время выполнения.

Итак, есть много мелких деталей, которые подробно описаны в следующей статье:

«Красивые отечественные библиотеки» (на Питоне)": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

Также есть одна возможность использовать Интроспекция GObject для библиотек, которые используют GLib.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top