【问题标题】:Cython extension class: How do I expose methods in the auto-generated C struct?Cython 扩展类:如何在自动生成的 C 结构中公开方法?
【发布时间】:2016-01-20 17:12:33
【问题描述】:

我现有的 C++ 代码定义了一些我需要使用的类,但我需要能够将这些类发送到 Python 代码。具体来说,我需要在 C++ 中创建类实例,创建 Python 对象作为这些 C++ 对象的包装器,然后将这些 Python 对象传递给 Python 代码进行处理。这只是一个更大的 C++ 程序的一部分,因此最终需要使用 C/Python API 在 C++ 中完成。

为了让我的生活更轻松,我使用 Cython 定义了扩展类(cdef 类),它们充当我的 C++ 对象的 Python 包装器。我使用的是典型格式,其中 cdef 类包含指向 C++ 类的指针,然后在创建 cdef 类实例时对其进行初始化。因为如果我有一个现有的 C++ 对象要包装,我还希望能够替换指针,我已将方法添加到我的 cdef 类到 accept() C++ 对象并获取它的指针。我的其他 cdef 类在 Cython 中成功使用了 accept() 方法,例如当一个对象拥有另一个对象时。

这是我的 Cython 代码示例:

MyCPlus.pxd

cdef extern from "MyCPlus.h" namespace "mynamespace":
    cdef cppclass MyCPlus_Class:
        MyCPlus_Class() except +

PyModule.pyx

cimport MyCPlus
from libcpp cimport bool

cdef class Py_Class [object Py_Class, type PyType_Class]:
    cdef MyCPlus.MyCPlus_Class* thisptr
    cdef bool owned

    cdef void accept(self, MyCPlus.MyCPlus_Class &indata):
        if self.owned:
            del self.thisptr
        self.thisptr = &indata
        self.owned = False

    def __cinit__(self):
        self.thisptr = new MyCPlus.MyCPlus_Class()
        self.owned = True

    def __dealloc__(self):
        if self.owned:
            del self.thisptr

当我尝试从 C++ 访问 accept() 方法时,问题就出现了。我尝试在我的 cdef 类和 accept() 方法上使用 publicapi 关键字,但我无法弄清楚如何在 Cython 自动生成的 .h 文件的 C 结构中公开此方法。无论我尝试什么,C 结构都是这样的:

PyModule.h(由 Cython 自动生成)

struct Py_Class {
  PyObject_HEAD
  struct __pyx_vtabstruct_11PyModule_Py_Class *__pyx_vtab;
  mynamespace::MyCPlus_Class *thisptr;
  bool owned;
};

我还尝试将self 输入输入为Py_Class,我什至尝试使用publicapi 关键字前向声明Py_Class。我还尝试将accept() 设为静态方法。我没有尝试过公开accept() 方法,以便我可以从C++ 中使用它。我确实尝试通过__pyx_vtab 访问它,但我得到一个编译器错误,“不完整类型的无效使用”。我已经搜索了很多,但还没有看到解决方案。谁能帮我?请,谢谢!

【问题讨论】:

  • static void __pyx_f_8PyModule_8Py_Class_accept(struct __pyx_obj_8PyModule_Py_Class *__pyx_v_self, mynamespace::MyCPlus_Class &__pyx_v_indata); /* proto*/ 看起来像是你可以使用的东西。
  • @DavidW,这可能会工作一段时间,但我真的在寻找如何告诉 Cython 将此方法放入我的 API 的解决方案。您在上面引用的行深埋在自动生成的.cpp 文件中,它可能会意外更改。我需要一个更易于维护的解决方案。不过,感谢您的尝试。
  • 你可以尝试的另一件事是在pxdpyx文件两个中声明accept()——如果你有一个完全声明的类方法(不仅仅是要公开的 extern 方法原型),并且 Cython 生成一个相应命名的可访问方法,您可以调用或覆盖
  • @fish2000 accept() 方法不是我原来的 C++ 类的一部分(我不能添加它),所以没有理由放入 pxd。我需要将它包含在我的扩展类的 C 结构中,这样我就可以为我的 C++ 对象创建一个扩展类包装器,然后我可以将其发送到 Python。这很有趣,因为 Cython 确实输入了 __pyx_vtab,但这似乎仅供 Cython 使用,不适用于外部 C/C++ 代码。我认为 Cython 还没有设计成这样做。

标签: python c++ cython


【解决方案1】:

正如您在comment 中指出的那样,__pyx_vtab 成员似乎仅供 Cython 使用,因为它甚至没有在导出的标头中为其定义结构类型。

除了您的回复之外,一种方法也可能是:

cdef api class Py_Class [object Py_Class, type Py_ClassType]:
    ...
    cdef void accept(self, MyCPlus.MyCPlus_Class &indata):
        ...  # do stuff here
    ...


cdef api void (*Py_Class_accept)(Py_Class self, MyCPlus.MyCPlus_Class &indata)
Py_Class_accept = &Py_Class.accept

基本上,我们定义一个函数指针并将其设置为我们想要公开的扩展方法。这与您回复的cdef'd 函数没有太大区别;主要区别在于我们可以像往常一样在类定义中定义我们的方法,而不必复制功能或方法/函数调用到另一个函数来公开它。需要注意的是,除了self 的扩展类型(在这种情况下)等之外,我们还必须将函数指针的签名几乎逐字逐句地定义为方法的签名;那么这也适用于常规功能。

请注意,我在 C 级 Cython .pyx 文件上对此进行了尝试,我没有也不打算在 CPP 实现文件上对其进行测试。但我猜希望这也能正常工作。

【讨论】:

    【解决方案2】:

    这并不是真正的解决方案,但我想出了一个解决方法来解决我的问题。我仍然希望有一个解决方案可以让我告诉 Cython 将 accept() 方法暴露给 C++。

    我的解决方法是为我的 Python 类(而不是方法)编写了一个单独的函数。然后,我将 api 关键字同时赋予了我的 Python 类和新函数:

    cdef api class Py_Class [object Py_Class, type PyType_Class]:
    (etc.)
    
    cdef api Py_Class wrap_MyCPlusClass(MyCPlus.MyCPlus_Class &indata):
        wrapper = Py_Class()
        del wrapper.thisptr
        wrapper.thisptr = &indata
        wrapper.owned = False
        return wrapper
    

    由于我需要包装的不同类的数量,这有点笨拙,但至少 Cython 将函数放在易于使用的 API 中:

    struct Py_Class* wrap_MyCPlusClass(mynamespace::MyCPlusClass &);
    

    【讨论】:

      【解决方案3】:

      在声明accept 时,您可能希望使用cpdef 而不是cdef。见the docs

      可从 Python 和 C 调用
      * 用 cpdef 语句声明。
      * 可以从任何地方调用,因为它使用了一点 Cython 魔法。
      * 从其他 Cython 代码调用时使用更快的 C 调用约定。

      试试看!

      【讨论】:

      • 我不需要从 Python 访问accept();仅来自 Cython 和 C++(我确实验证了它已经在 Cython 中工作)。我确实尝试过这个,但 Cython 给出了一个错误,因为 MyCPlus.MyCPlus_Class 不是 Python 对象。不过感谢您的尝试。
      • 哦,我的错误——只是好奇,如果你在没有显式类型声明的情况下再次尝试相同的操作,你会得到什么?我觉得pyx 文件中的类型注释信息应该是可选的并被视为“提示”,但我可能完全记错了。
      • 对于 Python 可访问的函数,我在 pyx 文件中使用静态类型来强制 Python 对输入进行自动转换(这也提供了一定程度的类型检查)。对于 C 可访问的函数,Cython 需要知道如何声明函数,因此可以使用静态类型来指定。如果您不指定,我认为它假定某些东西是 Python 对象。如果您指定了 Cython 扩展类,那么您还需要声明该类 publicapi,否则您的编译器将不知道它是什么。这就是为什么我尝试将这些关键字放在类和方法上。
      猜你喜欢
      • 1970-01-01
      • 2019-06-07
      • 2011-10-25
      • 1970-01-01
      • 2017-01-11
      • 2019-08-29
      • 2015-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多