用 Cython 包装许多相似的 cpp 类
Wrap many similar cpp classes with Cython
我需要用 Cython 包装类似的 cpp classes。我可以重复多次对一个 class 有效的方法,例如:
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...
为了包装其他 classes,除了第一行之外,我基本上必须编写相同的内容,并且可能会对一个或两个函数进行细微的更改。
我天真地以为我可以使用一个基数 class 并做类似
的事情
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 重复相同代码更好的解决方案 class?
一个糟糕的解决方案是使用 Cython 关键字 include
来包含函数的定义,但它确实很丑陋。
在 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 模板。正如我所写的那样,这应该在您的示例 (FFT2DWithFFTW1D
) 中创建 cdef class 加上一个基于 c++ [=27] 的额外 cdef class、OtherClass
=] OtherCppClass
以自己的名字导入。通过这种方式,您可以避免用一堆基本相同的 classes 写出一个长的 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 代码,因此您基本上可以随心所欲地自定义不同的 classes。对于上述情况,我认为这种使用 mako 实际上可能是对充满几乎相同包装器的长文件的可读性的改进。但是,如果事情变得太复杂,显然会失去可读性。
我需要用 Cython 包装类似的 cpp classes。我可以重复多次对一个 class 有效的方法,例如:
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...
为了包装其他 classes,除了第一行之外,我基本上必须编写相同的内容,并且可能会对一个或两个函数进行细微的更改。
我天真地以为我可以使用一个基数 class 并做类似
的事情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 重复相同代码更好的解决方案 class?
一个糟糕的解决方案是使用 Cython 关键字 include
来包含函数的定义,但它确实很丑陋。
在 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 模板。正如我所写的那样,这应该在您的示例 (FFT2DWithFFTW1D
) 中创建 cdef class 加上一个基于 c++ [=27] 的额外 cdef class、OtherClass
=] OtherCppClass
以自己的名字导入。通过这种方式,您可以避免用一堆基本相同的 classes 写出一个长的 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 代码,因此您基本上可以随心所欲地自定义不同的 classes。对于上述情况,我认为这种使用 mako 实际上可能是对充满几乎相同包装器的长文件的可读性的改进。但是,如果事情变得太复杂,显然会失去可读性。