如何在 cython 模块中使用外部包装类?

Posted

技术标签:

【中文标题】如何在 cython 模块中使用外部包装类?【英文标题】:How do I use an external wrapped class in cython module? 【发布时间】:2017-04-05 09:29:50 【问题描述】:

我有一个已经包装好的外部类(我的意思是,它可以通过 python 直接访问,无需进一步努力),现在我希望它成为更大的 cython 模块的 part(在换句话说,嵌入它)。

我可以明确地 python 导入它。但问题是外部类已经在 cython 模块中的 extern 函数中使用(因此该类最终在源代码中为 #included)。 Python import 需要编译模块,那么这两个模块可能有同一个类的两个不同副本...

我应该如何在 cython 中使用外部已经包装的类?


(可能过于简单化)示例:

Foo.cpp:

#include "Python.h"
#include "foo.hpp"

struct Foo_wrapper 
    PyObject_HEAD
    foo bar;
;

static int Foo_init(Foo_wrapper* self, PyObject* args, PyObject*) 
    ....


static PyTypeObject FooType ...;

垃圾邮件.pyx:

cdef extern from "some_functions.hpp":
    cdef some_function1(some_type); // this returns a wrapped foo object

def spam(arg1, arg2, arg3):
    // wrap arg1, arg2, arg3 to some_type
    return some_function1(an_instance_of_some_type); // huh? but foo isn't available here!

我想在spam.pyx 中使用 foo 类。

【问题讨论】:

认为 Python 导入是对的。我认为没有问题,除非两个定义最终不同(即编译模块 1,更改类,编译模块 2)。不过,这个问题可以用一个最小的完整示例来解决。 @DavidW,我不认为直接导入是正确的。但如果你这么说,我会试一试。我不认为因为你定义了两个空类 Foo 和 Bar,那么 Foo 与 Bar 是同一个逻辑类,但对解释器来说不是。或者如果你在两个不同的模块中定义了两个同名的类,它们仍然不一样。 这就是为什么一些代码的简单示例会有所帮助的原因。我不是 100% 清楚你实际上在做什么! @DavidW,请查看添加的示例。 这说明了很多事情。让我考虑一下。 【参考方案1】:

这应该没问题(几乎)。 cdef extern 这行不太对:

cdef extern from "some_functions.hpp":
    object some_function1(some_type); // this returns a wrapped foo object

注意对object 的更改 - 这告诉 Cython 该函数返回一个 Python 对象。 C/C++ 声明如下所示:

PyObject* some_function1(some_type);
// or
Foo_wrapper* some_function1(some_type);

两者都行。

Cython 代码可以使用它的原因是 PyObject_HEAD 包含一个指向 PyTypeObject FooType 的指针 ob_type。这是在创建对象时设置的。 PyTypeObject 包含 Python 解释器使用返回的对象所需的所有详细信息,因此一切正常。


整个东西基本上相当于Python代码:

# in "somemodule.py"
def a_function():
    import something_else
    return something_else.Class()

Python 解释器可以使用返回的值,尽管 Class 在“全局”命名空间中不为人所知。


需要注意的一件事是,在创建Foo_wrapper 之前,您应该确保至少调用了Foo 模块初始化函数一次。原因是这个函数通常会做一些事情,比如调用PyType_Ready(&FooType),以确保正确设置FooType。一个简单的方法是将以下行添加到some_function1

PyObject* m = PyImport_ImportModule("Foo");
if (m==NULL) return NULL; // an error
Py_CLEAR(m); // don't need to keep a reference to it

不过,还有其他方法可以做同样的事情。

【讨论】:

根据我对您的回答的理解,(如果我错了,请纠正我),我只需要 注册 类型以便解释器识别它。而注册是通过PyType_Ready完成的,一旦函数被调用,解释器就会知道类型。此外,如果本机完成,该函数应该在 cython 模块 init 函数中调用。对吗? 基本正确。如果 Foo.cpp 已经编译为 Python 模块,那么我认为在某处(可能在 some_function1 或从 Cython 中)进行模块导入比自己调用 PyType_Ready 更干净。如果 Foo.cpp 永远不会被编译成 Python 模块,那么从 Cython 模块的 init 函数调用 PyType_Ready(即在 .pyx 文件的全局范围内调用它)就可以了。 是否可以用 init 将其理想地包装为实际类?或者我将不得不使用一些工厂功能。 (是的,在这个例子中 some_function1 是一个工厂函数。) 您将a tp_init function 添加到TypeObject 是的,我有这个功能,但无法从模块中访问它。我只调用了 PyType_Ready,并没有将它添加到模块命名空间中。不过,我设法从 python 端添加了它。谢谢。

以上是关于如何在 cython 模块中使用外部包装类?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?

如何用 Cython 包装 C++ 类?

如何打包 cython 模块?

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

如何在 Cython 中返回新的 C++ 对象?

使用 cuda 的 cython 扩展