How to apply decorators to Cython cpdef functions
<p>I've been playing around with Cython lately and I came across this error when applying a decorator to a Cython function</p>

<p><code>Cdef functions/classes cannot take arbitrary decorators</code></p>

<p>Here is the code I was tinkering with:</p>

<pre><code>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 &lt; 2 else fib(n - 1) + fib(n - 2)
</code></pre>

<p>The error suggests that cdef functions can only take <em>certain</em> decorators. Is it possible to write your own decorators that you can apply to cdef functions?</p>

<hr>

<p>EDIT: For future readers:</p>

<p>The <code>g = plus_one(_g)</code> trick mentioned in @DavidW's answer <em>sort of</em> works. It does not work with recursion. e.g. doing <code>fib = memoize(fib)</code> in my example code does not memoize the recursive calls to fib, although it does memoize the top-level call. i.e. calling <code>fib(5)</code> will memoize the result of the <code>fib(5)</code> call, but it will <em>not</em> memoize the recursive calls (i.e. <code>fib(4), fib(3), fib(2), fib(1)</code>)</p>

<p>As @DavidW points out, <code>cdef, cpdef</code> functions are fully determined at compile time; the decoration is a runtime thing and does not update the actual function.</p>

<p><strong>No</strong> - you can't easily write decorators for <code>cdef</code> functions. The decorators <code>cdef</code> functions take are things like <code>cython.boundscheck</code> which control the Cython code generation rather than user generated functions.</p>

<p>The main difference between a <code>cdef</code> function and a <code>def</code> function is that a <code>cdef</code> function has a C interface while a <code>def</code> function becomes a Python callable so can be used from Python (but calling it is slightly less efficient because the arguments have to be passed in terms of PyObjects). [The inside of <em>both</em> a <code>cdef</code> and <code>def</code> function is compiled by Python so the only performance difference comes from the calling overhead] </p>

<p>The usual use of a decorator is to take an arbitrary Python callable and make some modification to it. For example</p>

<pre><code>def plus_one(f):
    def wrapper(*args,**kwargs):
       return f(*args,**kwargs) + 1
    return wrapper
</code></pre>

<p>now try to use it on a cdef function</p>

<pre><code>cdef int g(double x, double y):
    # some implementation...
</code></pre>

<p>The first problem is that g is translated to C code like <code>int g(double x, double y)</code> which can be represented by a function pointer but not as an arbitrary Python callable like <code>plus_one</code> expects. Second, <code>wrapper</code> has no way of knowing (from a C function pointer) what <code>g</code>s arguments are called (can't do <code>**kwargs</code>) or any easy way of doing the <code>*args</code> expansion.</p>

<p>You can make something like a decorator by taking a specific function pointer type and returning a Python callable:</p>

<pre><code>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
</code></pre>

<p><em>However,</em> you've lost the whole benefit of using a <code>cdef</code> function since <code>g</code> is now a generic Python callable with all the overhead that goes with it.</p>

<hr>

<p>Addendum: an alternative way of putting it is that decorators are a runtime Python feature (usually run at module import). <code>cdef</code> functions are a compile-time C feature. While it probably wouldn't be impossible to implement something like "compile-time decorators" it would be a pretty significant change to Cython.</p>

python cython cythonize