Mastering Memoization in Python. Understanding Function Caching in... | by Sadrach Pierre, Ph.D. | Towards Data Science

Memoization is a method used to store the results of previous function calls to speed up future calculations. If repeated function calls are made with the same parameters, we can store the previous values instead of repeating unnecessary calculations. This results in a significant speed up in calculations. In this post, we will use memoization to find factorials.
Let’s get started!
First, let’s define a recursive function that we can use to display the first factorials up to n. If you are unfamiliar with recursion, check out this article: Recursion in Python.
As a reminder, the factorial is defined for an integer n, such that it is the product of that integer and all integers below it. For example
1! = 1,
2! = 2*1= 2
3! = 3*2*1 = 6
and so forth. We can define the recursive function as follows:
def factorial(input_value):    if input_value < 2:         return 1    elif input_value >= 2:        return input_value * factorial(input_value-1)
Here we specify the base cases, which say that if the input value is less than 2 return 1. If the input value is greater than or equal to 2, return recursive function calls taking the product of the integer and previous integer values.
Now, let’s print the first 10 factorials:
for i in range(1, 11):     print(f"{i}! = ", factorial(i))
This seems to run fine. Now, let’s try displaying the first 5000 terms:
for i in range(1, 5000):     print(f"{i}! = ", factorial(i))
This returns the following error, which is a python guard against stack overflow (running out of memory):
This occurs because with each subsequent calculation we are doing repeated work.
Consider how the recursive function is calculating each term:
1! = 1,
2! = 2*1= 2
3! = 3*2! = 6
4! =4*3! = 24
Notice, for 4! we are repeating the calculation of 3! and 2!. If we had a way of remembering/storing those values upon calculating them, we’d avoid repeating calculations. This forms the motivation for the memoization method.
Let’s now walk through the steps of implementing the memoization method. To proceed, let’s initialize a dictionary:
factorial_dict = {}
Next, we will define our memoization function. First, we check if the input is less than 2 and return 1 if it is:
def factorial_memo(input_value):    if input_value < 2:         return 1
Next we check if the input value is in the dictionary. If it is not, we store the factorial value in the dictionary and return the value for the input key:
def factorial_memo(input_value):    ...    if input_value not in factorial_dict:        factorial_dict[input_value] = input_value * factorial_memo(input_value-1)    return factorial_dict[input_value]
The full function is:
def factorial_memo(input_value):    if input_value < 2:         return 1    if input_value not in factorial_dict:        factorial_dict[input_value] = input_value * factorial_memo(input_value-1)    return factorial_dict[input_value]
Now, let’s try displaying factorials up to 5000! with our new function:
for i in range(1, 5000):     print(f"{i}! = ", factorial_memo(i))
And we see that the calculation reaches 4999! successfully (output is truncated):
There is a simpler way to implement memoization using less code. Let’s consider our original recursive function:
def factorial(input_value):    if input_value < 2:         return 1    elif input_value >= 2:        return input_value * factorial(input_value-1)
We can import a decorator from the ‘functools’ module, called ‘lru_cache’, that allows us to cache our values. The name stands for “least recently used cache”. We can achieve the same performance as our ‘factorial_memo’ method using this decorator:
from functools import lru_cache@lru_cache(maxsize = 1000)def factorial(input_value):    if input_value < 2:         return 1    elif input_value >= 2:        return input_value * factorial(input_value-1)        for i in range(1, 5000):     print(f"{i}! = ", factorial(i))
We see that we achieve similar performance. I’ll stop here but I encourage you to play around with the code yourself.
To summarize, in this post we discussed the memoization method in python. First, we showed how the naive implementation of a recursive function becomes very slow after calculating many factorial terms. We then defined a new method where we stored past values that we’ve calculated in a dictionary. This leads to a significant speedup in calculations. We then discussed the ‘lru_cache’ decorator which allowed us to achieve a similar performance as our ‘factorial_memo’ method with less code. If you’re interested in learning more about memoization, I encourage you to check out Socratica’s YouTube tutorials. I hope you found this post useful/interesting. The code in this post is available on GitHub. Thank you for reading!