Skip to content

#210: Cache Function Calls

If our application uses recursion, we may call the same function with the same values many times. When that function is side-effect free, we can save a lot of time by caching the result. And even better, in Python we can do that without much effort.

A non-cached function

If we run this function to calculate the factorial of a number multiple times, we have to do the calculations each time (here we use a print() as a stand in for a more complex logic):

1
2
3
def factorial(n):
    print(f"***{n}***")
    return n * factorial(n-1) if n else 1
>>> factorial(5)
***5***
***4***
***3***
***2***
***1***
***0***
120
>>> factorial(6)
***6***
***5***
***4***
***3***
***2***
***1***
***0***
720

In our non-cached example, our function had to call print() 13 times.

The cached function

When we annotate our function from above with @cache and import that decorator from the built-in functools module, we can get the same results with only 7 calls to print():

1
2
3
4
5
6
from functools import cache

@cache
def factorial(n):
    print(f"***{n}***")
    return n * factorial(n-1) if n else 1
>>> factorial(5)
***5***
***4***
***3***
***2***
***1***
***0***
120
>>> factorial(6)
***6***
720

With the simple addition of @cache we reduced the method executions by nearly half. The more often we run our function, the bigger the saved execution time will be.

Conclusion

This little trick works great for many use-cases and can save you a lot of time. However, as with everything, there are some limitations. If your function has side-effects, you cannot cache them. When you get an error message, read it carefully. It may only need a switch from list to tuple in the return type to get you back on track.

Next week we look at regular expressions and how they work in Python.