Try putting print statements into the code to keep track of the state of known
:
def fibonacci(n):
print(n, known)
if n in known:
return known[n]
res = fibonacci(n-1) + fibonacci(n-2)
known[n] = res
return res
fibonacci(5)
print(known)
yields
5 {0: 0, 1: 1}
4 {0: 0, 1: 1}
3 {0: 0, 1: 1}
2 {0: 0, 1: 1}
1 {0: 0, 1: 1}
0 {0: 0, 1: 1}
1 {0: 0, 1: 1, 2: 1}
2 {0: 0, 1: 1, 2: 1, 3: 2}
3 {0: 0, 1: 1, 2: 1, 3: 2, 4: 3}
{0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5}
The first (integer) value is the value of n
. As you can see, fibonacci(5)
is
called, then fibonacci(4)
, then fibonacci(3)
, then fibonacci(2)
and so
on. These calls are all due to Python encountering
res = fibonacci(n-1) + fibonacci(n-2)
and recursively calling fibonacci(n-1)
. Remember, Python evaluates expressions
from left to right. So only after fibonacci(n-1)
returns is fibonacci(n-2)
called.
To better understand the order of the recursive function calls you could use
this decorator:
import functools
def trace(f):
"""This decorator shows how the function was called.
Especially useful with recursive functions."""
indent = ' ' * 2
@functools.wraps(f)
def wrapper(*arg, **kw):
arg_str = ', '.join(
['{0!r}'.format(a) for a in arg]
+ ['{0} = {1!r}'.format(key, val) for key, val in kw.items()])
function_call = '{n}({a})'.format(n=f.__name__, a=arg_str)
print("{i}--> {c}".format(
i=indent * (trace.level), c=function_call))
trace.level += 1
try:
result = f(*arg, **kw)
print("{i}<-- {c} returns {r}".format(
i=indent * (trace.level - 1), c=function_call, r=result))
finally:
trace.level -= 1
return result
trace.level = 0
return wrapper
known = {0:0, 1:1}
@trace
def fibonacci(n):
# print(n, known)
if n in known:
return known[n]
res = fibonacci(n-1) + fibonacci(n-2)
known[n] = res
return res
fibonacci(5)
print(known)
which yields
--> fibonacci(5)
--> fibonacci(4) # fibonacci(5) calls fibonacci(4)
--> fibonacci(3) # fibonacci(4) calls fibonacci(3)
--> fibonacci(2) # fibonacci(3) calls fibonacci(2)
--> fibonacci(1) # fibonacci(2) calls fibonacci(1)
<-- fibonacci(1) returns 1
--> fibonacci(0) # fibonacci(2) calls fibonacci(0)
<-- fibonacci(0) returns 0
<-- fibonacci(2) returns 1
--> fibonacci(1) # fibonacci(3) calls fibonacci(1)
<-- fibonacci(1) returns 1
<-- fibonacci(3) returns 2
--> fibonacci(2) # fibonacci(4) calls fibonacci(2)
<-- fibonacci(2) returns 1
<-- fibonacci(4) returns 3
--> fibonacci(3) # fibonacci(5) calls fibonacci(3)
<-- fibonacci(3) returns 2
<-- fibonacci(5) returns 5
{0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5}
You can tell by the level of indentation where each recursive call is coming from.