如何用Cython包装C ++类?

时间:2022-05-17 23:42:11

I have a C++ class. It's made up of one .ccp file and one .h file. It compiles (I can write a main method that uses it successfully in c++). How do I wrap this class with Cython to make it available in Python?

我有一个C ++类。它由一个.ccp文件和一个.h文件组成。它编译(我可以编写一个在c ++中成功使用它的main方法)。如何使用Cython包装此类以使其在Python中可用?

I've read the docs and don't follow. They talk about generating the cpp file. When I've tried to follow the docs, my already existing cpp gets blown away...

我已经阅读了文档但没有关注。他们谈论生成cpp文件。当我试图关注文档时,我已经存在的cpp被吹走了......

What am I meant to put in the pyx file? I've been told the class definition but how much of it? Just the public methods?

我打算把什么放在pyx文件中?我被告知课程定义,但有多少?只是公共方法?

Do I need a .pxd file? I don't understand when this file is or isn't required.

我需要.pxd文件吗?我不明白何时需要此文件。

I've tried asking these question in the #python IRC channel and can't get an answer.

我已经尝试在#python IRC频道中提出这些问题并且无法得到答案。

3 个解决方案

#1


14  

Even Cython is generally for use with C, it can generate C++ code, too. When compiling, you add the --cplus flag.

即使Cython通常与C一起使用,它也可以生成C ++代码。编译时,添加--cplus标志。

Now, creating a wrapper for the class is simple and not much different from wrapping a structure. It mainly differs from declaring the extern, but that's not much difference at all.

现在,为类创建包装器很简单,与包装结构没有多大区别。它主要不同于宣布外部,但这根本没有太大区别。

Suppose you have a class MyCppClass in mycppclass.h.

假设你在mycppclass.h中有一个MyCppClass类。

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

Note that the new keyword is only available when the --cplus flag is set, otherwise use malloc from <stdlib.h> by externing it.

请注意,new关键字仅在设置了--cplus标志时可用,否则通过externing使用 中的malloc。

Also note that you don't need to dereference the pointer (->) to call the method. Cython tracks the object's type and applies what fits.

另请注意,您无需取消引用指针( - >)即可调用该方法。 Cython跟踪对象的类型并应用适合的类型。

.pxd files are for seperating declarations from implementation, or to avoid namespace colliding. Imagine you'd like to name you Python-wrapper like the C++ class. Simply put in your .pxd file the extern declarations and cimport the pxd file in the .pyx.

.pxd文件用于从实现中分离声明,或避免命名空间冲突。想象一下,你想像C ++类一样命名Python包装器。只需在.pxd文件中输入extern声明,然后在.pyx中输入pxd文件。

cimport my_pxd
cdef my_pxd.SomeExternedType obj

Note that you can not write implementations in a .pxd file.

请注意,您无法在.pxd文件中编写实现。

#2


6  

So after lots of poking, trial and error, screaming and tearing my hair out, I finally got this to work. First though, I had to re-write my C++ into C, which for me really just involved converting all my std::string variables to char* and keeping track of some lengths.

因此经过大量的探索,试验和错误,尖叫和撕裂我的头发,我终于得到了这个工作。首先,我不得不将我的C ++重新编写成C语言,这对我来说实际上只涉及将所有std :: string变量转换为char *并跟踪一些长度。

Once done I had my .h and .c files. I wanted to make a single function from the C code available in Python. It turns out that Cython can compile your C files into the extension for you and link any libraries all in one go, so starting with my setup.py, it ended up looking like this:

完成后我得到了.h和.c文件。我想从Python中提供的C代码中创建一个函数。事实证明,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
)

As you can see, the second argument to the Extension simply lists all the files that need to be compiled, Cython works out how to compile them depending on their file extension as far as I can tell. The libraries array tells the Cython compiler what needs to be linked in (in this case I was wrapping some crypto stuff that I couldn't seem to mimick directly through existing Python libs).

正如您所看到的,Extension的第二个参数只列出了需要编译的所有文件,Cython根据文件扩展名计算出如何编译它们,据我所知。库数组告诉Cython编译器需要链接什么(在这种情况下,我正在包装一些我似乎无法通过现有Python库直接模仿的加密内容)。

To actually make my C function available in the .pyx file, you write a small wrapper in the .pxd. My myext.pxd looked as below:

要在.pyx文件中实际使我的C函数可用,可以在.pxd中编写一个小包装器。我的myext.pxd看起来如下:

cdef extern from "../stuff.h":
    char* myfunc(char* arg1, char* arg2, char* arg3)

In the .pyx you then use the cimport declaration to import this function, which is then available for use as if it were any other Python function:

在.pyx中,然后使用cimport声明来导入此函数,然后可以使用该函数,就好像它是任何其他Python函数一样:

cimport myext

def my_python_func(arg1, arg2, arg3):
    result = myext.myfunc(arg1, arg2, arg3)
    return result

When you build this (on Mac at least) you get a .so that you can import in python and run the functions from the .pyx. There may be better, more correct way to get this all working but that comes from experience and this was a first encounter that I managed to work out. I'd be very interested on pointers where I may have gone wrong.

当你构建它时(至少在Mac上),你得到一个.so,你可以在python中导入并运行.pyx中的函数。可能有更好,更正确的方法让这一切全部有效,但这来自经验,这是我设法解决的第一次遭遇。我对可能出错的指针非常感兴趣。

Update:

After further use of Cython, I found it was super simple to integrate it with C++ too, once you know what you're doing. Making C++'s string available is as simple as from libcpp.string cimport string in your pyx/pyd. Declaring the C++ class is similarly easy as:

在进一步使用Cython之后,我发现将它与C ++集成起来非常简单,一旦你知道你在做什么。使C ++的字符串可用就像pyx / pyd中的libcpp.string cimport字符串一样简单。声明C ++类同样容易:

cdef extern from "MyCPPClass.h":
    cdef cppclass MyCPPClass:
        int foo;
        string bar;

Sure you have to basically redeclare the .h definition of your class in a Pythonic format, but that's a small price to pay for getting access to your already written C++ functions.

当然,您必须以Pythonic格式重新声明类的.h定义,但这对于访问已编写的C ++函数来说是一个很小的代价。

#3


3  

Cython is mainly for C development, to integrate C++ with Python I would recommend Boost.Python. Their excellent documentation should get you started pretty quickly.

Cython主要用于C开发,要将C ++与Python集成,我建议使用Boost.Python。他们出色的文档可以让你很快开始。

#1


14  

Even Cython is generally for use with C, it can generate C++ code, too. When compiling, you add the --cplus flag.

即使Cython通常与C一起使用,它也可以生成C ++代码。编译时,添加--cplus标志。

Now, creating a wrapper for the class is simple and not much different from wrapping a structure. It mainly differs from declaring the extern, but that's not much difference at all.

现在,为类创建包装器很简单,与包装结构没有多大区别。它主要不同于宣布外部,但这根本没有太大区别。

Suppose you have a class MyCppClass in mycppclass.h.

假设你在mycppclass.h中有一个MyCppClass类。

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

Note that the new keyword is only available when the --cplus flag is set, otherwise use malloc from <stdlib.h> by externing it.

请注意,new关键字仅在设置了--cplus标志时可用,否则通过externing使用 中的malloc。

Also note that you don't need to dereference the pointer (->) to call the method. Cython tracks the object's type and applies what fits.

另请注意,您无需取消引用指针( - >)即可调用该方法。 Cython跟踪对象的类型并应用适合的类型。

.pxd files are for seperating declarations from implementation, or to avoid namespace colliding. Imagine you'd like to name you Python-wrapper like the C++ class. Simply put in your .pxd file the extern declarations and cimport the pxd file in the .pyx.

.pxd文件用于从实现中分离声明,或避免命名空间冲突。想象一下,你想像C ++类一样命名Python包装器。只需在.pxd文件中输入extern声明,然后在.pyx中输入pxd文件。

cimport my_pxd
cdef my_pxd.SomeExternedType obj

Note that you can not write implementations in a .pxd file.

请注意,您无法在.pxd文件中编写实现。

#2


6  

So after lots of poking, trial and error, screaming and tearing my hair out, I finally got this to work. First though, I had to re-write my C++ into C, which for me really just involved converting all my std::string variables to char* and keeping track of some lengths.

因此经过大量的探索,试验和错误,尖叫和撕裂我的头发,我终于得到了这个工作。首先,我不得不将我的C ++重新编写成C语言,这对我来说实际上只涉及将所有std :: string变量转换为char *并跟踪一些长度。

Once done I had my .h and .c files. I wanted to make a single function from the C code available in Python. It turns out that Cython can compile your C files into the extension for you and link any libraries all in one go, so starting with my setup.py, it ended up looking like this:

完成后我得到了.h和.c文件。我想从Python中提供的C代码中创建一个函数。事实证明,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
)

As you can see, the second argument to the Extension simply lists all the files that need to be compiled, Cython works out how to compile them depending on their file extension as far as I can tell. The libraries array tells the Cython compiler what needs to be linked in (in this case I was wrapping some crypto stuff that I couldn't seem to mimick directly through existing Python libs).

正如您所看到的,Extension的第二个参数只列出了需要编译的所有文件,Cython根据文件扩展名计算出如何编译它们,据我所知。库数组告诉Cython编译器需要链接什么(在这种情况下,我正在包装一些我似乎无法通过现有Python库直接模仿的加密内容)。

To actually make my C function available in the .pyx file, you write a small wrapper in the .pxd. My myext.pxd looked as below:

要在.pyx文件中实际使我的C函数可用,可以在.pxd中编写一个小包装器。我的myext.pxd看起来如下:

cdef extern from "../stuff.h":
    char* myfunc(char* arg1, char* arg2, char* arg3)

In the .pyx you then use the cimport declaration to import this function, which is then available for use as if it were any other Python function:

在.pyx中,然后使用cimport声明来导入此函数,然后可以使用该函数,就好像它是任何其他Python函数一样:

cimport myext

def my_python_func(arg1, arg2, arg3):
    result = myext.myfunc(arg1, arg2, arg3)
    return result

When you build this (on Mac at least) you get a .so that you can import in python and run the functions from the .pyx. There may be better, more correct way to get this all working but that comes from experience and this was a first encounter that I managed to work out. I'd be very interested on pointers where I may have gone wrong.

当你构建它时(至少在Mac上),你得到一个.so,你可以在python中导入并运行.pyx中的函数。可能有更好,更正确的方法让这一切全部有效,但这来自经验,这是我设法解决的第一次遭遇。我对可能出错的指针非常感兴趣。

Update:

After further use of Cython, I found it was super simple to integrate it with C++ too, once you know what you're doing. Making C++'s string available is as simple as from libcpp.string cimport string in your pyx/pyd. Declaring the C++ class is similarly easy as:

在进一步使用Cython之后,我发现将它与C ++集成起来非常简单,一旦你知道你在做什么。使C ++的字符串可用就像pyx / pyd中的libcpp.string cimport字符串一样简单。声明C ++类同样容易:

cdef extern from "MyCPPClass.h":
    cdef cppclass MyCPPClass:
        int foo;
        string bar;

Sure you have to basically redeclare the .h definition of your class in a Pythonic format, but that's a small price to pay for getting access to your already written C++ functions.

当然,您必须以Pythonic格式重新声明类的.h定义,但这对于访问已编写的C ++函数来说是一个很小的代价。

#3


3  

Cython is mainly for C development, to integrate C++ with Python I would recommend Boost.Python. Their excellent documentation should get you started pretty quickly.

Cython主要用于C开发,要将C ++与Python集成,我建议使用Boost.Python。他们出色的文档可以让你很快开始。