如何将装饰器应用于Cython cpdef函数

时间:2016-09-16 21:53:14

标签: python cython cythonize

我最近一直在玩Cython并且在将装饰器应用到Cython函数时遇到了这个错误

Cdef functions/classes cannot take arbitrary decorators

以下是我正在修补的代码:

import functools

def memoize(f):
    computed = {}
    @functools.wraps(f)
    def memoized_f(main_arg, *args, **kwargs):
        if computed.get(main_arg):
            return computed[main_arg]
        computed[main_arg] = f(main_arg, *args, **kwargs)
        return computed[main_arg]
    return memoized_f

@memoize
cpdef int fib(int n):
    return 1 if n < 2 else fib(n - 1) + fib(n - 2)

该错误表明cdef函数只能使用某些装饰器。是否可以编写可以应用于cdef函数的装饰器?

编辑:对于未来的读者:

@ DavidW回答中提到的g = plus_one(_g)技巧有效。它不适用于递归。例如在我的示例代码中执行fib = memoize(fib)不会记住对fib的递归调用,尽管它会记住顶级调用。即,呼叫fib(5)会记住fib(5)来电的结果,但不会记住递归电话(即fib(4), fib(3), fib(2), fib(1)

正如@DavidW指出的那样,cdef, cpdef函数在编译时完全确定;装饰是一个运行时的东西,不会更新实际的功能。

1 个答案:

答案 0 :(得分:6)

- 您无法轻松为cdef函数编写装饰器。装饰器cdef函数采用cython.boundscheck之类的东西来控制Cython代码生成而不是用户生成的函数。

cdef函数和def函数之间的主要区别在于cdef函数具有C接口,而def函数成为Python可调用函数,因此可以从Python中使用(但调用它的效率稍低,因为参数必须根据PyObjects传递)。 [ 一个cdefdef函数的内部由Python编译,因此唯一的性能差异来自调用开销]

装饰器的通常用法是采用任意Python可调用并对其进行一些修改。例如

def plus_one(f):
    def wrapper(*args,**kwargs):
       return f(*args,**kwargs) + 1
    return wrapper

现在尝试在cdef函数

上使用它
cdef int g(double x, double y):
    # some implementation...

第一个问题是g被转换为像int g(double x, double y)这样的C代码,它可以用函数指针表示,但不能像plus_one期望的那样任意Python可调用。其次,wrapper无法知道(来自C函数指针)调用g个参数(不能做**kwargs)或任何简单的方法{ {1}}扩张。

你可以通过采用特定的函数指针类型并返回Python可调用来创建类似装饰器的东西:

*args

然而,你已经失去了使用cdef plus_one(int (*f)(double, double): def wrapper(double x, double y): return f(x, y) + 1 return wrapper cdef int _g(double x, double y): # some implementation g = plus_one(_g) # kind of like a decorator 函数的全部好处,因为cdef现在是一个通用的Python可调用的,它带有所有开销

附录:另一种方法是装饰器是运行时Python功能(通常在模块导入时运行)。 g函数是编译时C函数。虽然可能不可能实现类似&#34;编译时装饰器&#34;这对Cython来说是一个非常重要的变化。