Create decorator for recursion count

Viewed 41

I need to create a decorator to call Ackermann function and write down its attributes: the number of recursions and execution time.

I tried the below code but it works wrong.

Could anyone help?

def visualise(func):
    visualise.level = 0
    @wraps (func)
    def wrapper(*args, **kwargs):
         start=time.perf_counter()
         visualise.level += 1
         calls = func(*args, **kwargs)
         visualise.level -= 1
         last_time_taken=time.perf_counter()-start
         setattr(func,'calls',calls)
         setattr(func,'last_time_taken',last_time_taken)
         print(last_time_taken)
         return calls
    return wrapper

@visualise
def ackermann(n, m):
    ...

ackermann(2, 2)
ackermann.last_time_taken
ackermann.calls

The result should be ackermann.last_time_taken = 0.00000034 seconds (for example) and ackermann.calls = 7.

1 Answers

Here's a minimal version of your code:

from functools import wraps


def visualise(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        setattr(func, "bar", 42) # or func.bar = 42

    return wrapper


@visualise
def foo():
    ...


foo()
print(foo.bar) # => AttributeError: 'function' object has no attribute 'bar'

How to debug?

We can start by making sure the attribute can really be set on a function:

from functools import wraps


def visualise(func):
    setattr(func, "bar", 42)
    return func


@visualise
def foo():
    ...


foo()
print(foo.bar) # => 42

OK, that worked. Next, let's go back to the wrapped code and make sure we're adding the property to the correct function:

from functools import wraps


def visualise(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(2, func) # => 2 <function foo at 0x00000248F38EFA30>
        setattr(func, "bar", 42)

    print(1, wrapper) # => 1 <function foo at 0x00000248F38EFAC0>
    return wrapper


@visualise
def foo():
    ...


foo()
print(3, foo) # => 3 <function foo at 0x00000248F38EFAC0>
print(foo.bar) # => AttributeError: 'function' object has no attribute 'bar'

There's the problem; you're adding the property to the original function, but trying to access the property on the decorated function. That's a totally different object.

Fix it:

from functools import wraps


def visualise(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        setattr(wrapper, "bar", 42) # wrapper, not func

    return wrapper


@visualise
def foo():
    ...


foo()
print(foo.bar) # => 42

See also Counting recursive calls of a function

Related