Here's another answer using a decorator. The advantage of using a decorator is that the base function fib
does not need to change. That means the total_count
code and multiple return values can be discarded from your original attempt -
@counter(model = fib_result)
def fib(n = 0):
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
Our decorator counter
accepts a model
so we can respond to the behavior of the decorated function. Our decorated fib
will return a fib_result
such as { result: ?, count: ? }
. That means we also need to handle fib(?) + fib(?)
which is why we also defined __add__
-
class fib_result:
def __init__(self, result, count = 0):
if isinstance(result, fib_result):
self.result = result.result
self.count = result.count + count
else:
self.result = result
self.count = count
def __add__(a, b):
return fib_result(a.result + b.result, a.count + b.count)
As you can see, this fib_result
is specific to counting fib
calls. A different model may be required for counting other recursive functions that return other types of results.
All that's left now is to define our generic counter
decorator. Our decorator takes arguments and returns a new decorator, lambda f: ...
, which simply captures the passed arguments, lambda *args: ...
, and constructs a new model
with the result of f(*args)
and a base count of 1
-
def counter(model = tuple):
return lambda f: lambda *args: model(f(*args), 1)
Here's how the complete program works -
r = fib(4)
print(f"answer: {r.result}, recurs: {r.count}")
# answer: 3, recurs: 9
r = fib(10)
print(f"answer: {r.result}, recurs: {r.count}")
# answer: 55, recurs: 177