Cython:python class 包装器中的模板

Cython: templates in python class wrappers

问题

有没有办法用模板为 Cython 包装的 C++ class 创建一个 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>> 那样实例化 classes:融合类型有,毫不奇怪,没有递归的概念。

改写

我想要实现的是包装的 class,例如:

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 本身不支持它,但也许有人可以想出解决方法。我不是在寻找惯用的或好的例子,我只是想知道这是否有可能。

正如你所说,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 文件中,您只需“包含”每个 class 的生成文件。 Cython 中的“include”命令是文字文本包含(如 C 预处理器)并需要一个 .pxi 文件:

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

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

你必须选择 classes 在编译时生成,但这是不可避免的(模板是编译时的特性),所以你永远无法从中动态生成它们Python 脚本环境,因为不会生成相应的 C++ class。

其他选项

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

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

    std::vector-like class 为例。它的许多成员不依赖于模板类型,因此可以存在于一个公共基础中(可能作为纯虚函数?)。 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 class 来包装这个

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

    和特定派生 Python class 是 取决于类型的功能。

    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 混合搭配(通过导入包含模板 classes 的生成模块)。