Как связать класс С++ с Cython?
У меня есть класс С++. Он состоит из одного файла .ccp и одного файла .h. Он компилирует (я могу написать основной метод, который успешно использует его в С++). Как я могу связать этот класс с Cython, чтобы сделать его доступным в Python?
Я читал документы и не слежу. Они говорят о создании файла cpp. Когда я пытался следить за документами, мой уже существующий cpp сбрасывается...
Что я должен поставить в файл pyx? Мне сказали определение класса, но сколько его? Просто общественные методы?
Нужен ли мне файл .pxd? Я не понимаю, когда этот файл нужен или не требуется.
Я пробовал задавать этот вопрос на #python IRC-канале и не могу получить ответ.
Ответы
Ответ 1
Итак, после многих попыток, проб и ошибок, крича и вырвав мои волосы, я наконец получил это, чтобы работать. Во-первых, мне пришлось переписать мой С++ в C, который для меня действительно просто включал преобразование всех моих переменных std::string
в char*
и отслеживание некоторых длин.
После этого у меня были файлы .h и .c. Я хотел сделать одну функцию из C-кода, доступного в Python. Оказывается, Cython может скомпилировать ваши C файлы в расширение для вас и связать любые библиотеки за один раз, поэтому, начиная с моей setup.py, это выглядело так:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("myext",
["myext.pyx", "../stuff.c"],
libraries=["ssl", "crypto"]
)
]
setup(
name = "myext",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
Как вы можете видеть, второй аргумент Extension просто перечисляет все файлы, которые необходимо скомпилировать, Cython разрабатывает, как их компилировать, в зависимости от их расширения файла, насколько я могу судить. Массив библиотек сообщает компилятору Cython, с чем нужно связать (в этом случае я обертывал некоторые криптографические материалы, которые я не мог бы имитировать непосредственно через существующие библиотеки Python).
Чтобы сделать мою C-функцию доступной в файле .pyx, вы пишете небольшую обертку в .pxd. Мой myext.pxd выглядел следующим образом:
cdef extern from "../stuff.h":
char* myfunc(char* arg1, char* arg2, char* arg3)
В .pyx вы затем используете объявление cimport для импорта этой функции, которая затем доступна для использования, как если бы это была любая другая функция Python:
cimport myext
def my_python_func(arg1, arg2, arg3):
result = myext.myfunc(arg1, arg2, arg3)
return result
Когда вы создаете это (по крайней мере на Mac), вы получаете .so, что вы можете импортировать в python и запускать функции из .pyx. Там может быть лучший, более правильный способ заставить все это работать, но это исходит из опыта, и это стало первой встречей, с которой мне удалось разобраться. Я был бы очень заинтересован в указателях, где я, возможно, ошибся.
Update:
После дальнейшего использования Cython я обнаружил, что было супер просто интегрировать его с С++, как только вы знаете, что делаете. Создание С++ string
доступно так же просто, как from libcpp.string cimport string
в вашем pyx/pyd. Объявление класса С++ аналогично просто:
cdef extern from "MyCPPClass.h":
cdef cppclass MyCPPClass:
int foo;
string bar;
Уверен, что вам нужно в основном переопределить .h определение вашего класса в формате Pythonic, но это небольшая цена, чтобы заплатить за получение доступа к уже написанным функциям С++.
Ответ 2
Даже Cython обычно используется с C, он также может генерировать код на С++. При компиляции вы добавляете флаг --cplus
.
Теперь создание оболочки для класса прост и не сильно отличается от обертывания структуры. Это в основном отличается от объявления extern
, но это не так много.
Предположим, что у вас есть класс MyCppClass
в mycppclass.h
.
cdef extern from "mycppclass.h":
cppclass MyCppClass:
int some_var
MyCppClass(int, char*)
void doStuff(void*)
char* getStuff(int)
cdef class MyClass:
# the public-modifier will make the attribute public for cython,
# not for python. Maybe you need to access the internal C++ object from
# outside of the class. If not, you better declare it as private by just
# leaving out the `private` modifier.
# ---- EDIT ------
# Sorry, this statement is wrong. The `private` modifier would make it available to Python,
# so the following line would cause an error es the Pointer to MyCppClass
# couldn't be converted to a Python object.
#>> cdef public MyCppClass* cobj
# correct is:
cdef MyCppClass* obj
def __init__(self, int some_var, char* some_string):
self.cobj = new MyCppClass(some_var, some_string)
if self.cobj == NULL:
raise MemoryError('Not enough memory.')
def __del__(self):
del self.cobj
property some_var:
def __get__(self):
return self.cobj.some_var
def __set__(self, int var):
self.cobj.some_var = var
Обратите внимание, что ключевое слово new
доступно только тогда, когда установлен флаг --cplus
, в противном случае используйте malloc
из <stdlib.h>
путем его externing.
Также обратите внимание, что вам не нужно разыменовывать указатель (->
), чтобы вызвать метод. Cython отслеживает тип объекта и применяет то, что подходит.
.pxd файлы предназначены для разделения объявлений от реализации или для предотвращения столкновения пространства имен. Представьте, что вы хотите назвать Python-оберткой, как класс С++. Просто добавьте в свой .pxd файл объявления extern
и cimport
файл pxd в .pyx.
cimport my_pxd
cdef my_pxd.SomeExternedType obj
Обратите внимание, что вы не можете записывать реализации в файл .pxd.
Ответ 3
Cython в основном для разработки C, для интеграции С++ с Python я бы рекомендовал Boost.Python. Их отличная документация должна начать работать довольно быстро.