Recently, fell into this trap as I wanted to speed up a slow instance method by caching it.
When you decorate an instance method with functools.lru_cache decorator, the instances
of the class encapsulating that method never get garbage collected within the lifetime of
the process holding them.
Let’s consider this example:
# src.py
import functools
import time
from typing import TypeVar
Number = TypeVar("Number", int, float, complex)
class SlowAdder:
def __init__(self, delay: int = 1) -> None:
self.delay = delay
@functools.lru_cache
def calculate(self, *args: Number) -> Number:
time.sleep(self.delay)
return sum(args)
def __del__(self) -> None:
print("Deleting instance ...")
# Create a SlowAdder instance.
slow_adder = SlowAdder(2)
# Measure performance.
start_time = time.perf_counter()
# ----------------------------------------------
result = slow_adder.calculate(1, 2)
# ----------------------------------------------
end_time = time.perf_counter()
print(f"Calculation took {end_time-start_time} seconds, result: {result}.")
start_time = time.perf_counter()
# ----------------------------------------------
result = slow_adder.calculate(1, 2)
# ----------------------------------------------
end_time = time.perf_counter()
print(f"Calculation took {end_time-start_time} seconds, result: {result}.")
Here, I’ve created a simple SlowAdder class that accepts a delay value; then it sleeps
for delay seconds and calculates the sum of the inputs in the calculate method. To avoid
this slow recalculation for the same arguments, the calculate method was wrapped in the
lru_cache decorator. The __del__ method notifies us when the garbage collection has
successfully cleaned up instances of the class.