如何用 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++ 对象?
在 ipython 中使用 Cython 包装 C++ 标准库
使用 Cython 时如何将一个 C++ 类(引用)传递给另一个?