【问题标题】:Cython: templates in python class wrappersCython:python 类包装器中的模板
【发布时间】:2021-12-31 00:09:36
【问题描述】:

问题

有没有办法使用模板为 Cython 包装的 C++ 类创建 Python 包装器? (即完全按照此处显示的内容进行操作,但使用模板:http://docs.cython.org/src/userguide/wrapping_CPlusPlus.html#create-cython-wrapper-class)。

我知道融合类型的解决方法 (https://groups.google.com/forum/#!topic/cython-users/qQpMo3hGQqI),但这不允许您实例化像 vector<vector<int>> 这样的类:毫无疑问,融合类型没有递归的概念。

改写

我想要实现的是一个像这样的包装类:

cdef extern from "header.h":
    cdef cppclass Foo[T]:
        Foo(T param)
        # ...

创建一个简单的 Python 包装器:

cdef class PyFoo[T]:  # I know the '[T]' can't be here, it's a wish
    cdef Foo[T] *thisptr
    def __cinit__(self, param):
        self.thisptr = new Foo[T](param)
    # ...

我很确定 Cython 本身不支持这一点,但也许有人可以想到一个解决方法。我不是在寻找惯用的或好的例子,我只是想知道这是否有可能。

【问题讨论】:

  • 如何通过某种字符串替换运行它来为每个T 生成不同的源文件?
  • 所以本质上做 C++ 编译器对模板所做的事情,但对于 cython 包装器?我已经考虑过了。这绝对是我正在考虑的一个选项,尽管它以某种方式扩展了 cython 编译器并且做了大量工作。
  • 我想我的意思要简单得多:1) 创建一个包含cdef class PyFoo_{T}: cdef Foo[{T}] *thisptr 等的文件。2) 通过执行.format(T='std::vector<int>') 生成一堆cython 文件,并保存结果。 3) 将这些文件包含在您的主要 Cython 文件中(语法 include "filename.pxi")。您显然必须选择要手动生成的类,但您总是必须这样做。
  • 哦,你是对的,它要简单得多。还有一件更棘手的事情:如何知道要生成哪些“实例化”?我必须事先知道所有用法(再次,就像 C++ 一样)。但是,似乎没有更好的选择。
  • 嗯,你需要在编译 Cython 代码时就全部了解它们(从 Python 解释器按需动态生成它们当然是不可能的)。

标签: python c++ templates cython


【解决方案1】:

正如你所说,Cython 并不真正支持这一点。

我认为到目前为止最简单的方法就是使用字符串替换手动生成一堆 Cython 文件。从“foowrapper.pxi.src”文件开始(名称随心所欲...):

cdef class PyFoo_{T}:
  cdef Foo[{T}] *thisptr
  def __cinit__(self, param):
    self.thisptr = new Foo[{T}](param)
    # etc

接下来,通过一个简单的程序(也可能是 Python)运行它来加载文件,进行字符串替换,并以新名称再次保存文件。关键是:

output = code.format(T=T) # where T is a string with a C++ class name 
              # e.g. "int" or "std::vector<double>"

(显然有一些与加载和保存有关的代码我已经跳过了懒惰)

然后,在您的 Cython 文件中,您只需“包含”为每个类生成的文件。 Cython 中的“包含”命令是文字文本包含(如 C 预处理器),需要一个 .pxi 文件:

cdef extern from "header.h":
    cdef cppclass Foo[T]:
        Foo(T param)
        # ...

include "foowrapper_int.pxi"
include "foowrapper_vectordouble.pxi
# etc

您必须在编译时选择要生成的类,但这是不可避免的(模板是编译时功能),因此您永远无法从 Python 脚本环境动态生成它们,因为相应的不会生成 C++ 类。

其他选项

其他几个选项值得简要考虑。

  1. 您可以从不依赖于模板参数的基类(比如FooBase)继承Foo&lt;T&gt;。然后,您将 FooBase 包装在 Cython 中(为您关心的情况生成类似构造函数的函数)。只有当您要调用的函数没有依赖于模板类型的参数时,这才是真正可行的。显然这也涉及更改 C++ 代码。

    以std::vector-like 类为例。它的许多成员不依赖于模板类型,因此可以存在于一个公共基础中(可能作为纯虚函数?)。 Cython cdef extern 可能看起来像:

    cdef extern from "somewhere.h":
        cdef cppclass VectorBase:
            int size()
            void pop_back()
        cdef cppclass Vector[T](VectorBase):
            void push_back(T)
    

    然后您可以定义一个基础 Python 类来包装它

    cdef class PyVectorBase:
        cdef VectorBase* vb
        def size(self):
            return self.vb.size()
        def pop_back(self):
            self.vb.pop_back()
    

    执行的函数的特定派生 Python 类取决于类型。

    cdef class PyVectorDouble(PyVectorBase):
        def __cinit__(self):
            self.vb = new Vector[double]()
        def push_back(self, value):
            cdef Vector[double]* vd = <Vector[double]*>(self.vb)  # cast is OK because we constructed it...
            vd.push_back(value)
    

    取决于有多少“模板相关”参数,这可以节省大量重复。

  2. 看看另一种包装方式。 Boost Python 肯定会原生支持这一点(但也有其自身的缺点)。我想 SIP/SWIG 也会应付(但我不知道)。如有必要,您可以相当干净地将这些与 Cython 混合和匹配(通过导入包含模板类的生成模块)。

【讨论】:

  • 我在研究中似乎错过了 Boost Python,我需要仔细看看。另外:很好的答案,真的澄清了很多事情,谢谢。
  • 我知道这是一个较旧的答案,但如果你能写一个如何从基类继承的例子,那就太好了
  • @AlexSp3 我添加了一个简单的继承示例。我怀疑它并不能解决你的问题
  • @DavidW 谢谢!明白了,感激不尽
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-05
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
  • 1970-01-01
相关资源
最近更新 更多