Cython:Python类包装器中的模板

12

问题

有没有办法创建一个Python包装器,用于带模板的Cython包装C++类?(即像这里显示的一样,但使用模板: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本身不支持这个功能,但也许有人能想出一些解决办法。我并不是在寻找成语化或漂亮的示例,我只是想知道是否有任何可能性。


1
把它运行一下通过某种字符串替换生成每个 T 的不同源文件怎么样? - DavidW
那么本质上就是为Cython包装器做C++编译器为模板所做的事情?我已经考虑过了。尽管这是在某种程度上扩展Cython编译器并需要相当多的工作,但这绝对是我正在考虑的选项。 - piotrMocz
好的,你需要在编译Cython代码时就知道它们所有的内容(从Python解释器动态地按需生成它们是不可能的)。 - DavidW
最后一个想法 - 你是否考虑过为所有 Foo<T> 实例使用一个公共基类。根据您的要求,这可能是可能或不可能的,但如果可以的话,您只需包装基类和几个构造函数即可。 - DavidW
这是一个有趣的想法,我会考虑一下。总的来说,你能否把你的建议编写成一个答案,这样我就可以标记它为已接受? :) - piotrMocz
显示剩余2条评论
1个回答

13

正如你所说,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. You could inherit Foo<T> from a base class (say FooBase) which doesn't depend on the template parameter. You'd then wrap FooBase in Cython (generating constructor-like functions for the cases you care about). This is only really viable if the functions you want to call don't have arguments that depend on the template type. Obviously this also involves changing the C++ code.

    Consider as an example a std::vector-like class. Many of its members don't depend on the template type so could exist in a common base (maybe as pure virtual functions?). The Cython cdef extern might look like:

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

    You could then define a base Python class to wrap this

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

    and specific derived Python classes for the functions that do depend on the type.

    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)
    

    depending on how many "template-dependent" parameters there are this could save significant duplication.

  2. Look at a different way of wrapping. Boost Python will certainly support this natively (but comes with its own disadvantages). I imagine SIP/SWIG would also cope (but I don't know). You can could fairly cleanly mix-and-match these with Cython if necessary (by importing the generated module containing your template classes).


我似乎在研究中错过了Boost Python,我需要更仔细地研究一下。另外:非常好的答案,真正澄清了许多事情,谢谢。 - piotrMocz
我知道这是一个较旧的答案,但如果您能编写一个从基类继承的示例,那将非常棒。 - AlexSp3
@AlexSp3 我已经添加了一个简单的继承示例。我怀疑这并不能解决你的问题。 - DavidW
@DavidW 谢谢!我明白了,非常感谢。 - AlexSp3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接