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。
其他选项
其他几个选项值得简要考虑。
您可以从不依赖于模板参数的基础 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)
取决于有多少“模板相关”参数,这可以节省大量重复。
换一种包装方式。 Boost Python 肯定会原生支持它(但有其自身的缺点)。我想 SIP/SWIG 也能应付(但我不知道)。如有必要,您可以相当干净地将它们与 Cython 混合搭配(通过导入包含模板 classes 的生成模块)。
问题
有没有办法用模板为 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。
其他选项
其他几个选项值得简要考虑。
您可以从不依赖于模板参数的基础 class(比如
FooBase
)继承Foo<T>
。然后,您将FooBase
包装在 Cython 中(为您关心的情况生成类似构造函数的函数)。只有当您要调用的函数没有依赖于模板类型的参数时,这才真正可行。显然这也涉及到更改C++代码。以
std::vector
-like class 为例。它的许多成员不依赖于模板类型,因此可以存在于一个公共基础中(可能作为纯虚函数?)。 Cythoncdef 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)
取决于有多少“模板相关”参数,这可以节省大量重复。
换一种包装方式。 Boost Python 肯定会原生支持它(但有其自身的缺点)。我想 SIP/SWIG 也能应付(但我不知道)。如有必要,您可以相当干净地将它们与 Cython 混合搭配(通过导入包含模板 classes 的生成模块)。