14

我最近一直在玩 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 函数的自己的装饰器?


编辑:对于未来的读者:

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

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

4

1 回答 1

15

——你不能轻易地为cdef函数编写装饰器。装饰器cdef函数采用的是cython.boundscheck控制 Cython 代码生成的东西,而不是用户生成的函数。

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

装饰器的通常用途是获取任意 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 被翻译成 C 代码int g(double x, double y),可以用函数指针表示,但不能像plus_one预期的那样作为任意 Python 可调用代码。其次,wrapper无法知道(从 C 函数指针)g调用了哪些参数(不能做**kwargs)或任何简单的*args扩展方法。

您可以通过采用特定的函数指针类型并返回 Python 可调用对象来制作类似装饰器的东西:

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函数的全部好处,因为g现在是一个通用的 Python 可调用函数,伴随着它的所有开销。


附录:另一种说法是装饰器是 Python 的运行时特性(通常在模块导入时运行)。cdef函数是编译时 C 功能。虽然实现诸如“编译时装饰器”之类的东西可能并非不可能,但这对 Cython 来说将是一个非常重大的变化。

于 2016-09-17T08:18:31.033 回答