🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Guia completo de functools Python 2026: lru_cache, parcial e redução

⏱️5 min read  ·  899 words

O módulo functools do Python é um dos módulos de biblioteca padrão mais subutilizados, porém poderosos. Em 2026, functools fornece ferramentas para funções de ordem superior, memoização, aplicação parcial e composição de funções que eliminam clichês e tornam o código mais expressivo. Este guia completo cobre todas as funções do functools com exemplos práticos.

functools.lru_cache e cache

from functools import lru_cache, cache
import time

# Simple memoization — cache indefinitely
@cache  # Python 3.9+, same as lru_cache(maxsize=None)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# LRU cache — keep only most recent N results
@lru_cache(maxsize=128)
def expensive_api_call(user_id: int) -> dict:
    time.sleep(0.5)  # simulate slow API
    return {"id": user_id, "name": "User"}

# Cache info and management
print(fibonacci.cache_info())  # CacheInfo(hits=X, misses=Y, maxsize=None, currsize=N)
fibonacci.cache_clear()

# Method-level cache (instance-level, not shared across instances)
class DataService:
    @lru_cache(maxsize=256)
    def get_user(self, user_id: int) -> dict:
        return fetch_from_db(user_id)

functools.partial — Aplicação Parcial

from functools import partial
import operator

# Create a new function with some args pre-filled
def power(base: float, exponent: float) -> float:
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5))  # 25.0
print(cube(3))    # 27.0

# Useful with map/filter
double = partial(operator.mul, 2)
numbers = [1, 2, 3, 4, 5]
doubled = list(map(double, numbers))  # [2, 4, 6, 8, 10]

# Partial with keyword args
import requests
get_from_api = partial(requests.get, headers={"Authorization": "Bearer token123"})
response = get_from_api("https://api.example.com/users")

# In configuration/dependency injection
from functools import partial

def create_user(db, email: str, role: str = "user") -> dict:
    return db.insert("users", {"email": email, "role": role})

create_admin = partial(create_user, db=prod_db, role="admin")
admin = create_admin("admin@example.com")

functools.reduzir

from functools import reduce
import operator

# Sum a list (use sum() instead in practice)
total = reduce(operator.add, [1, 2, 3, 4, 5])  # 15

# Product of list (no built-in)
product = reduce(operator.mul, [1, 2, 3, 4, 5])  # 120

# Flatten nested list
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(operator.add, nested)  # [1, 2, 3, 4, 5, 6]

# Compose functions (right to left)
def compose(*functions):
    return reduce(lambda f, g: lambda x: f(g(x)), functions)

pipeline = compose(str.upper, str.strip, str.lower)
print(pipeline("  Hello World  "))  # "HELLO WORLD"

# Deep dictionary merge
dicts = [{"a": 1}, {"b": 2, "c": 3}, {"d": 4}]
merged = reduce(lambda acc, d: {**acc, **d}, dicts)  # {"a":1,"b":2,"c":3,"d":4}

functools.wraps

from functools import wraps
import time, logging

logger = logging.getLogger(__name__)

def log_calls(func):
    @wraps(func)  # preserves __name__, __doc__, __wrapped__, __annotations__
    def wrapper(*args, **kwargs):
        logger.info(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        logger.info(f"Done {func.__name__}")
        return result
    return wrapper

def measure(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__}: {elapsed*1000:.2f}ms")
        return result
    return wrapper

@log_calls
@measure
def process_data(data: list) -> list:
    return sorted(data)

# Without @wraps, process_data.__name__ would be "wrapper"
print(process_data.__name__)  # "process_data" (correct!)

functools.total_ordering

from functools import total_ordering

@total_ordering  # generates __le__, __lt__, __ge__, __gt__ from __eq__ and one comparison
class Version:
    def __init__(self, major: int, minor: int, patch: int = 0):
        self.major = major
        self.minor = minor
        self.patch = patch

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

    def __lt__(self, other: "Version") -> bool:
        return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

    def __repr__(self) -> str:
        return f"Version({self.major}.{self.minor}.{self.patch})"

versions = [Version(2, 0), Version(1, 9, 5), Version(3, 0), Version(1, 9, 4)]
print(sorted(versions))  # [1.9.4, 1.9.5, 2.0, 3.0]
print(Version(1, 9) < Version(2, 0))  # True
print(Version(3, 0) >= Version(2, 1))  # True (generated!)

functools.singledispatch — Sobrecarga de método

from functools import singledispatch

@singledispatch
def process(obj):
    raise TypeError(f"Cannot process {type(obj)}")

@process.register(int)
def _(obj: int) -> str:
    return f"Integer: {obj}"

@process.register(str)
def _(obj: str) -> str:
    return f"String: '{obj}'"

@process.register(list)
def _(obj: list) -> str:
    return f"List with {len(obj)} items"

@process.register(dict)
def _(obj: dict) -> str:
    return f"Dict with {len(obj)} keys: {list(obj.keys())}"

print(process(42))           # "Integer: 42"
print(process("hello"))      # "String: 'hello'"
print(process([1, 2, 3]))    # "List with 3 items"

# Useful for serialization/display of varied types without isinstance chains

functools.cached_property

from functools import cached_property
import statistics

class Dataset:
    def __init__(self, data: list[float]):
        self._data = data

    @cached_property
    def mean(self) -> float:
        print("Computing mean...")
        return statistics.mean(self._data)

    @cached_property
    def stdev(self) -> float:
        print("Computing stdev...")
        return statistics.stdev(self._data)

    @cached_property
    def sorted_data(self) -> list[float]:
        print("Sorting...")
        return sorted(self._data)

ds = Dataset([3, 1, 4, 1, 5, 9, 2, 6])
print(ds.mean)       # "Computing mean..." then value
print(ds.mean)       # value directly (cached)
print(ds.stdev)      # "Computing stdev..." then value

Functools Python em 2026 são essenciais para um código limpo e eficiente. Use @cache para memoização, parcial() para injeção de configuração, @wraps para transparência do decorador, @total_ordering para classes de comparação, @singledispatch para envio baseado em tipo e cached_property para atributos computados lentamente. Essas ferramentas substituem o padrão e esclarecem a intenção.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা