如何用 Cython 包装 C++ 类?

Posted

技术标签:

【中文标题】如何用 Cython 包装 C++ 类?【英文标题】:How do I wrap a C++ class with Cython? 【发布时间】:2012-01-19 21:05:27 【问题描述】:

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

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

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

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

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

【问题讨论】:

如果您的问题已被回答,请将此问题标记为已回答。否则,请再次说明您的问题。 @NiklasR 您的回答涵盖了我的一些困惑领域,但并没有完全让我明白。我给了你一票,但打算在我有空的时候写一个完整的答案。 【参考方案1】:

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

现在,为类创建包装器很简单,与包装结构没有太大区别。主要区别于声明extern,但其实差别不大。

假设您在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

请注意,new 关键字仅在设置了 --cplus 标志时可用,否则通过外部化使用 <stdlib.h> 中的 malloc

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

.pxd 文件用于将声明与实现分开,或避免命名空间冲突。想象一下,您想将 Python 包装器命名为 C++ 类。只需将extern 声明和cimport .pyx 中的 pxd 文件放入您的 .pxd 文件中。

cimport my_pxd
cdef my_pxd.SomeExternedType obj

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

【讨论】:

所以我的 C++ 使用了来自 OpenSSL 的功能。这些函数是我需要在我的 pxd 中声明的吗? 另外,我可以将 C++ 转换为 C,但这并没有真正帮助我进一步解决问题...... 任何你想在 Cython 中使用的函数都需要在 Cython 范围内声明。它可以在您的 .pyx.pxd 文件中声明,这取决于项目的复杂性以保持其整洁,以及您是否希望将 Python 函数命名为与其 C++ 原件完全相同。 cdef MyCppClass* obj 看起来像 C++ 中的 static member。这样做有什么意义?【参考方案2】:

因此,经过大量的戳、试错、尖叫和撕扯我的头发,我终于把它弄好了。不过,首先,我必须将我的 C++ 重新编写为 C,对我而言,这实际上只是将我所有的 std::string 变量转换为 char* 并跟踪一些长度。

完成后,我就有了 .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
)

如您所见,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 运行函数。可能有更好、更正确的方法来让这一切正常工作,但这来自经验,这是我设法解决的第一次遭遇。我会对可能出错的指针非常感兴趣。

更新:

在进一步使用 Cython 之后,我发现将它与 C++ 集成也非常简单,只要您知道自己在做什么。使 C++ 的 string 可用就像在 pyx/pyd 中的 from libcpp.string cimport string 一样简单。声明 C++ 类同样简单:

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

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

【讨论】:

请帮帮我!我真的很想将 Python 3.3 与 Cython 和 C++ 一起使用,你可能有一些示例文件吗?我安装了最新的 Cython 和 Python。 我没有任何 Python 3 的示例文件,但是,请确保您正确设置了编译器标志wiki.cython.org/FAQ#WhatPythonversionsdoesCythonsupport.3F 在 SO 上提出一个问题,了解您遇到的更具体的问题。如果您在 cmets 中将其链接到此处,我可以查看一下。 不确定这是否会特别有用,但我写了this Python 3.3 wrapper for a C++ library。 仅供参考,现在支持 std::string 以及 char*【参考方案3】:

Cython 主要用于 C 开发,将 C++ 与 Python 集成我会推荐 Boost.Python。他们出色的文档应该可以帮助您快速入门。

【讨论】:

除了 Boost.Python、Shinboken(来自 PySide 项目)和 SIP(​​来自 PyQT 项目),我相信 SWIG 也可以包装 C++ 对象。理论上你可以用 Cython 包装 C++ 代码,但你必须使用混乱的名称并手工做很多事情——我自己不推荐它。 由于我对 Boost 及其许多库的质量有非常的高度评价,因此在给出选项列表时,它通常是我的首选。至于您提出的选项:PySide 相对较新,就我而言,未经证实。 SIP 采用 GPL 许可证,这可能会阻止某些人。我对 SWIG 有过一些不愉快的经历,所以我自己不能推荐它。与往常一样,YMMV,但在我看来,Boost 几乎不会出错。 @spatz 很抱歉,我不能同意 Boost.Python 的优秀文档,甚至一般的 Boost。我发现他们的文档在过去完全具有误导性或完全缺失。几个月前我正在使用他们的网络/套接字库,这是大量的试验和错误。 @Endophage,我并不是说 Boost 的所有文档都很好,我只是指的是 Boost.Python 文档。虽然其他 Boost 库通常缺乏文档(但它们在受欢迎程度和在线帮助方面弥补了这一不足),但我发现 Boost.Python 相当不错。 @spatz 好吧,我再看看花药,但大约 6 个月前我试图使用 Boost.Python 来做其他事情,甚至可以编译 hello world 示例。我在 Mac 上仅供参考,所以有时文档并没有完全对齐......【参考方案4】:

以上答案或多或少回答了OP的问题。

但是现在已经过了 2020 年年中,我想在这里为那些想要通过可以“安装 pip”的 python 包探索 Python-C++ 绑定的人做出贡献(以资源摘要的形式)(或在 PyPI 中)。

我认为从 Python 调用 C++ / 包装第三方库,可以查看 cppyy。这是一个相对年轻的项目,但它确实看起来很有前途,并有现代编译器的支持。请参阅以下链接: https://cppyy.readthedocs.io/en/latest/

另外,总是有 pybind11 .... https://github.com/pybind/pybind11

Cython 还原生支持 C++(对于大多数 C++ 语言);欲了解更多信息,请参阅https://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html

【讨论】:

以上是关于如何用 Cython 包装 C++ 类?的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个包装的对象返回 Cython 中的包装 C++ 对象?

使用 Cython 包装 C++ 类时处理指针

在 ipython 中使用 Cython 包装 C++ 标准库

使用 Cython 时如何将一个 C++ 类(引用)传递给另一个?

用函数指针包装 C++ 代码作为 cython 中的模板参数

将多个 C++ 类包装成一个 Python 类