Memoizare cu @cache și Polimorfism cu @singledispatch în functools în Python

Modulul functools Python 3.9+

Ghid complet pentru optimizări funcționale în Python: memoizare ultra-rapidă cu functools.cache și functools.lru_cache și implementarea polimorfismului curat cu singledispatch.

Sintaxă
from functools import cache, singledispatch
@cache
def fib(n): ...
@singledispatch
def serializeaza(obj): ...

Modulul standard functools furnizează funcții de ordin superior care acționează asupra altor funcții. Două dintre cele mai utile instrumente pentru cod robust și rapid sunt @cache (pentru memoizarea automată a rezultatelor costisitoare) și @singledispatch (pentru implementarea polimorfismului funcțional elegant, fără lanțuri repetitive de if isinstance(...)).

1. Memoizare cu @cache (Python 3.9+) și @lru_cache
from functools import cache, lru_cache

# @cache creează un cache infinit în memorie bazat pe argumentele funcției:
@cache
def calculeaza_factori_primi(numar: int) -> list[int]:
    print(f"Calcul costisitor pentru {numar}...")
    factori = []
    d = 2
    temp = numar
    while d * d <= temp:
        if temp % d == 0:
            factori.append(d)
            temp //= d
        else:
            d += 1
    if temp > 1:
        factori.append(temp)
    return factori

# Primul apel: execută calculul
print(calculeaza_factori_primi(840)) # Rulează algoritmul

# Al doilea apel cu aceleași argumente: returnează INSTANTANEU din cache-ul RAM!
print(calculeaza_factori_primi(840))

# Verificarea statisticilor de cache:
print(calculeaza_factori_primi.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)

@cache vs. @lru_cache: Când folosim fiecare?

  • @cache: Stochează nelimitat toate rezultatele. Folosiți-l doar dacă spațiul de intrare este mic sau finit (de exemplu, funcții matematice discrete sau citirea unor configurații statice).
  • @lru_cache(maxsize=128): Folosește politica Least Recently Used. Când cache-ul atinge dimensiunea maximă (de exemplu 128 sau 1024 de intrări), cele mai vechi elemente neutilizate sunt eliminate automat pentru a proteja memoria RAM a serverului.

2. Polimorfism Elegant cu @singledispatch

Deseori trebuie să scriem funcții care procesează datele diferit în funcție de tipul primit (de exemplu, o funcție de formatare sau de serializare). Un lanț clasic de if isinstance(x, int): ... elif isinstance(x, list): ... este greu de extins și încalcă principiul Open-Closed din SOLID.

Serializator polimorfic folosind @singledispatch
from functools import singledispatch
from datetime import datetime, date

# 1. Definim funcția de bază pentru cazul generic (fallback):
@singledispatch
def formateaza(valoare) -> str:
    return str(valoare)

# 2. Înregistrăm implementări specializate pentru fiecare tip:
@formateaza.register
def _(valoare: int) -> str:
    return f"{valoare:,} RON".replace(",", ".")

@formateaza.register
def _(valoare: date) -> str:
    return valoare.strftime("%d/%m/%Y")

@formateaza.register
def _(valoare: list) -> str:
    return "[" + ", ".join(formateaza(x) for x in valoare) + "]"

# Testare:
print(formateaza(1500000))        # "1.500.000 RON"
print(formateaza(date(2026, 10, 24))) # "24/10/2026"
print(formateaza([100, 200]))     # "[100 RON, 200 RON]"