通过python接口传递cython函数

时间:2017-09-04 13:28:18

标签: python scipy cython

可以从Python脚本将cdef Cython函数传递给另一个(python def)cython函数吗?

最小例子:

test_module.pyx

cpdef min_arg(f, int N):
    cdef double x = 100000.
    cdef int best_i = -1

    for i in range(N):
        if f(i) < x:
            x = f(i)
            best_i = i
    return best_i

def py_f(x):
    return (x-5)**2

cdef public api double cy_f(double x):
    return (x-5)**2

test.py

import pyximport; pyximport.install()
import testmodule

testmodule.min_arg(testmodule.py_f, 100)

这很有效,但我希望能够做到

testmodule.min_arg(testmodule.cy_f, 100)
来自test.py的

,具有cython的速度(每个f(i)调用没有Python开销)。但显然,Python并不了解cy_f,因为它不是defcpdef声明的。

我希望存在这样的事情:

from scipy import LowLevelCallable
cy_f = LowLevelCallable.from_cython(testmodule, 'cy_f')
testmodule.min_arg(cy_f, 100)

但这会给TypeError: 'LowLevelCallable' object is not callable

提前谢谢。

1 个答案:

答案 0 :(得分:1)

LowLevelCallable是一类必须由底层Python模块接受的函数。这项工作已经完成了几个模块,包括正交例程scipy.integrate.quad

如果您希望使用相同的包装方法,则必须完成使用它的SciPy例程,例如scipy.ndimage.generic_filter1dscipy.integrate.quad。但是,代码位于已编译的扩展中。

另一种方法是,如果你的问题被合理地定义为回调,那就是自己实现这个。我在我的一个代码中完成了这个,所以为了简单起见,我发布了链接:

  1. .pxd文件中,我定义了界面cyfunc_d_dhttps://github.com/pdebuyl/skl1/blob/master/skl1/core.pxd
  2. 我可以在“基础”cython模块https://github.com/pdebuyl/skl1/blob/master/skl1/euler.pyx以及“用户定义”模块中重复使用此界面。
  3. 最终代码允许简单的“cython-cython”调用,同时允许在Cython级别传递对象

    我根据您的问题调整了代码:

    1. test_interface.pxd

      cdef class cyfunc:                                                                                                                         
          cpdef double f(self, double x)                                                                                                         
      
      cdef class pyfunc(cyfunc):                                                                                                                 
          cdef object py_f                                                                                                                       
          cpdef double f(self, double x)                                                                                                         
      
    2. test_interface.pyx

      cdef class cyfunc:
          cpdef double f(self, double x):
              return 0
          def __cinit__(self):
              pass
      
      
      cdef class pyfunc(cyfunc):
          cpdef double f(self, double x):
              return self.py_f(x)
          def __init__(self, f):
              self.py_f = f
      
    3. setup.py

      from setuptools import setup, Extension                                                                                                    
      from Cython.Build import cythonize                                                                                                         
      
      setup(                                                                                                                                     
          ext_modules=cythonize((Extension('test_interface', ["test_interface.pyx"]),                                                            
                                Extension('test_module', ["test_module.pyx"]))                                                                   
                            )                                                                                                                    
      )                                                                                                                                          
      
    4. test_module.pyx

      from test_interface cimport cyfunc, pyfunc                                                                                                 
      
      cpdef min_arg(f, int N):                                                                                                                   
          cdef double x = 100000.                                                                                                                
          cdef int best_i = -1                                                                                                                   
          cdef int i                                                                                                                             
          cdef double current_value                                                                                                              
      
          cdef cyfunc py_f                                                                                                                       
      
          if isinstance(f, cyfunc):                                                                                                              
              py_f = f                                                                                                                           
              print('cyfunc')                                                                                                                    
          elif callable(f):                                                                                                                      
              py_f = pyfunc(f)                                                                                                                   
              print('no cyfunc')                                                                                                                 
          else:                                                                                                                                  
              raise ValueError("f should be a callable or a cyfunc")                                                                             
      
          for i in range(N):                                                                                                                     
              current_value = py_f.f(i)                                                                                                          
              if current_value < x:                                                                                                              
                  x = current_value                                                                                                              
                  best_i = i                                                                                                                     
          return best_i                                                                                                                          
      
      def py_f(x):                                                                                                                               
          return (x-5)**2                                                                                                                        
      
      cdef class cy_f(cyfunc):                                                                                                                   
          cpdef double f(self, double x):                                                                                                        
              return (x-5)**2                                                                                                                    
      
    5. 使用:

      python3 setup.py build_ext --inplace
      python3 -c 'import test_module ; print(test_module.min_arg(test_module.cy_f(), 10))'
      python3 -c 'import test_module ; print(test_module.min_arg(test_module.py_f, 10))'