用Cython包装许多类似的cpp类

时间:2015-11-19 10:11:57

标签: c++ cython

我需要用Cython包装类似的cpp类。我可以重复多次对一个类有用的东西,例如:

from mycpplib import FFT2DWithFFTW1D as mycppclass

cdef class FFT2DWithFFTW1D:
    cdef mycppclass* thisptr

    def __cinit__(self, int n0=2, int n1=2):
        self.thisptr = new mycppclass(n0, n1)

    def __dealloc__(self):
        self.thisptr.destroy()
        del self.thisptr

    def get_local_size_X(self):
        return self.thisptr.get_local_size_X()

    # many other functions...

要包装其他类,我基本上必须编写除第一行之外的相同内容,并且可能在一个或两个函数中稍作修改。

我天真地认为我可以使用基类并执行类似

的操作
class BaseFFT2D:
    def get_local_size_X(self):
        return self.thisptr.get_local_size_X()

    # many other functions...

cdef class FFT2DWithFFTW1D(BaseFFT2D):
    cdef mycppclass* thisptr

    def __cinit__(self, int n0=2, int n1=2):
        self.thisptr = new mycppclass(n0, n1)

    def __dealloc__(self):
        self.thisptr.destroy()
        del self.thisptr

但当然这不起作用,因为thisptr不是Python对象。由于这只是一个cpp指针,我不能使用Cython关键字public来使Python可以访问它。

有没有比为每个cpp类重复相同代码更好的解决方案?

一个糟糕的解决方案是使用Cython关键字include来包含函数的定义,但实际上非常难看。

1 个答案:

答案 0 :(得分:2)

在Cython中没有这个解决方案(据我所知,我之前看过一点)。但是,我认为以下(未经测试,但我认为基本上是正确的)基于mako的解决方案非常干净。 Mako是一个python模板引擎,通常用于生成网页,但也可用于生成Python或Cython代码。

<%!
cpp_wrappers = [('FFT2DWithFFTW1D', 'FFT2DWithFFTW1D', 'mycppclass'), 
                     ('OtherClass', 'OtherCppClass', 'OtherCppClass')]
%>

% for _, importname, cppname in cpp_wrappers:
from mycpplib import ${importname} as ${cppname}
% endfor

% for classname, _, cppname in cpp_wrappers:
cdef class ${classname}:
    cdef ${cppname}* thisptr

    def __cinit__(self, int n0=2, int n1=2):
        self.thisptr = new ${cppname}(n0, n1)

    def __dealloc__(self):
        self.thisptr.destroy()
        del self.thisptr

    def get_local_size_X(self):
        return self.thisptr.get_local_size_X()

    # many other functions...

% endfor

基本上,它是您要编写的Cython代码的mako模板。正如我写的那样,这应该在你的例子中创建cdef类(FFT2DWithFFTW1D)加上一个额外的cdef类OtherClass,基于以自己名义导入的c ++类OtherCppClass 。通过这种方式,您可以避免使用一堆基本相同的类来编写一个很长的Cython文件。

我喜欢让mako编译模板(创建那个长的cython文件)作为setup.py的一部分。例如,我可能有一些基于以下最小脚本的东西(也是未经测试的,但至少给出了基本想法):

from setuptools import setup, Extension
from mako.lookup import TemplateLookup
from Cython.Distutils import build_ext
from Cython.Build import cythonize

# Set which files will be compiled
mako_files = [('modulename.mako.pyx','modulename.pyx')]
cython_files = [('modulename', 'modulename.pyx')]

# Compile mako template(s)
lookup = TemplateLookup(directories = ['.'])
for template_file, output_file in mako_files:
    template = lookup.get_template(template_file)
    with open(output_file, 'w') as outfile:
        outfile.write(template.render())

# Compile Cython files
ext_modules = []
for module_name, file_name in cython_files:
    ext_modules.append(Extension(module_name, [file_name]))
ext_modules = cythonize(ext_modules)

# Standard setup stuff
opts = dict(name='mypackage',
            requires=['cython', 'mako'],
            ext_modules=ext_modules,
            cmdclass={'build_ext': build_ext})

if __name__ == '__main__':
    setup(**opts)

Cython已经添加了一个额外的编译阶段,其中.pyx文件被编译为c或c ++。此解决方案在此之前添加了另一个阶段,其中mako模板被编译为Cython文件。您可以在mako中使用任意Python代码,因此您可以根据自己的需要定制不同的类。对于上面的情况,我认为mako的这种使用实际上可能是在一个充满几乎相同的包装器的长文件中的可读性的改进。但是,如果事情变得太复杂,可读性显然会丢失。