Cómo aplicar decoradores a las funciones cpdef de Cython.

He estado jugando con Cython últimamente y encontré este error al aplicar un decorador a una función de Cython

Cdef functions/classes cannot take arbitrary decorators

Aquí está el código con el que estaba jugando:

 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) 

El error sugiere que las funciones cdef solo pueden tomar ciertos decoradores. ¿Es posible escribir sus propios decoradores que puede aplicar a las funciones cdef?


EDITAR: Para los futuros lectores:

    El truco de g = plus_one(_g) mencionado en el tipo de respuesta de @ DavidW funciona. No funciona con la recursividad. por ejemplo, hacer fib = memoize(fib) en mi código de ejemplo no memoriza las llamadas recursivas a fib, aunque sí memoriza la llamada de nivel superior. es decir, llamar a fib(5) memorizará el resultado de la llamada de fib(5) , pero no memorizará las llamadas recursivas (es decir, fib(4), fib(3), fib(2), fib(1) )

    Como señala cdef, cpdef funciones cdef, cpdef están completamente determinadas en el momento de la comstackción; la decoración es una cosa del tiempo de ejecución y no actualiza la función real.

    No , no se pueden escribir fácilmente decoradores para cdef funciones cdef . Los decoradores que cdef funciones cdef son cosas como cython.boundscheck que controlan la generación del código de Cython en lugar de las funciones generadas por el usuario.

    La diferencia principal entre una función cdef y una función def es que una función cdef tiene una interfaz C, mientras que una función def convierte en Python que se puede llamar, por lo que se puede usar desde Python (pero llamarlo es un poco menos eficiente porque los argumentos se deben pasar) términos de PyObjects). [Python comstack el interior de las funciones cdef y def , por lo que la única diferencia de rendimiento proviene de la sobrecarga de llamadas]

    El uso habitual de un decorador es tomar un llamable Python arbitrario y modificarlo. Por ejemplo

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

    ahora trata de usarlo en una función cdef

     cdef int g(double x, double y): # some implementation... 

    El primer problema es que g se traduce a un código C como int g(double x, double y) que puede representarse mediante un puntero de función, pero no como un python arbitrario que se puede plus_one como se espera de plus_one . En segundo lugar, la wrapper no tiene forma de saber (a partir de un puntero de función C) a qué argumentos de g se llaman (no puedo hacer **kwargs ) o cualquier forma fácil de hacer la expansión *args .

    Puedes hacer algo como un decorador tomando un tipo de puntero de función específico y devolviendo un Python que se puede llamar:

     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 

    Sin embargo, ha perdido todo el beneficio de usar una función cdef , ya que g es ahora un Python genérico con todos los gastos generales que conlleva.


    Addendum: una forma alternativa de decirlo es que los decoradores son una característica de Python en tiempo de ejecución (generalmente se ejecutan en la importación del módulo). cdef funciones cdef son una característica C en tiempo de comstackción. Si bien probablemente no sería imposible implementar algo como “decoradores en tiempo de comstackción”, sería un cambio bastante significativo para Cython.