Aprovecha las funciones como objetos de primera clase para escribir decoradores robustos, parametrizables y transparentes.
Abrir esta lección en KodokonEn Python, una función es un objeto como cualquier otro: puedes asignarla a una variable, pasarla como argumento, devolverla. Una función de orden superior recibe o devuelve una función - sorted(data, key=...) es una de ellas. Añade los cierres (closures), donde una función interna captura las variables de la función que la envuelve y las mantiene vivas después de que esta termine, y ya tienes todos los ingredientes de los decoradores.
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10)) # 20 30Un decorador es una función que recibe una función y devuelve otra - normalmente un wrapper que añade comportamiento alrededor de la llamada. La sintaxis @timed encima de def slow_sum es puro azúcar sintáctico para slow_sum = timed(slow_sum). El wrapper acepta *args, kwargs para mantenerse agnóstico respecto a la firma, y devuelve** el resultado de la función decorada - olvidarlo es un error clásico que convierte cada valor de retorno en None.
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
delay = time.perf_counter() - start
print(f"{func.__name__}: {delay:.4f}s")
return result
return wrapper
@timed
def slow_sum(n):
return sum(range(n))
print(slow_sum(1_000_000))Para parametrizar un decorador (@repeat(times=3)), hace falta un nivel más: una fábrica que recibe los parámetros y devuelve el decorador, que a su vez devuelve el wrapper. Tres funciones anidadas - ese es el precio de la flexibilidad. Si el estado se complica, una clase con un método __call__ suele ser más legible. Reserva los decoradores para los asuntos transversales: registro, caché, reintento, control de acceso. Un decorador que altera la lógica de negocio le oculta lo esencial a quien lee.
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello {name}")
greet("Ada")