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

How to apply decorators to Cython cpdef functions

我最近一直在玩 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 函数在编译时完全确定;装饰是运行时的事情,不会更新实际功能。

- 您无法轻松地为 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)或任何简单的方法来做 *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 可调用函数随之而来的所有开销。


附录:另一种表达方式是装饰器是 运行time Python 特性(通常是 运行 在模块导入时)。 cdef 函数是一个编译时 C 特性。虽然实现类似 "compile-time decorators" 的东西可能并非不可能,但这对 Cython 来说是一个非常重要的改变。