En el concepto anterior vimos como crear decoradores simples que pueden envolver funciones y ejecutar código antes o después de ellas.
Pero, ¿qué pasa cuando queremos que nuestros decoradores sean configurables, es decir, que acepten parámetros propios? Aquí es donde entran los decoradores con argumentos.
Un decorador normal solo recibe la función a decorar:
def mi_decorador(func): def envoltura(*args, **kwargs): print("Antes de la función") resultado = func(*args, **kwargs) print("Después de la función") return resultado return envoltura
Si queremos pasar parámetros adicionales al decorador, necesitamos un nivel extra de funciones. Esto se debe a que Python primero evalúa los paréntesis del decorador antes de aplicarlo a la función.
def decorador_parametrizado(mensaje): def decorador(func): def envoltura(*args, **kwargs): print(f"{mensaje} - Antes de la función") resultado = func(*args, **kwargs) print(f"{mensaje} - Después de la función") return resultado return envoltura return decorador
Primer nivel (decorador_parametrizado): recibe los argumentos que queremos pasar al decorador.
Segundo nivel (decorador): recibe la función a decorar.
Tercer nivel (envoltura): se ejecuta cuando llamamos a la función decorada y puede manipular sus argumentos o resultados.
def decorador_parametrizado(mensaje): def decorador(func): def envoltura(*args, **kwargs): print(f"{mensaje} - Antes de la función") resultado = func(*args, **kwargs) print(f"{mensaje} - Después de la función") return resultado return envoltura return decorador @decorador_parametrizado("Depuracion") def principal(): print("Función principal ejecutándose.") principal()
@decorador_parametrizado("Depuracion") def principal(): print("Función principal ejecutándose.")
Python primero evalúa decorador_parametrizado("Depuracion"), que devuelve la función decorador.
Luego aplica decorador(principal), que devuelve envoltura.
Por lo tanto, la función principal queda reemplazada por envoltura.
Llamada a la función decorada
principal()
Cuando llamamos principal(), en realidad se ejecuta la función envoltura:
Imprime:
Depuracion - Antes de la función
Ejecuta la función original principal(), que imprime:
Función principal ejecutándose.
Imprime:
Depuracion - Después de la función
Creación de un decorador que tenga dos parámetros que limite valores numéricos entre un rango.
def limitar_valores(min_val, max_val): """Decorador que asegura que todos los argumentos numéricos estén entre min_val y max_val.""" def decorador(func): def envoltura(*args, **kwargs): for i, arg in enumerate(args): if not (arg >=min_val and arg <= max_val): raise ValueError(f"Argumento {arg} en posición {i} fuera del rango [{min_val}, {max_val}]") print(f"Todos los argumentos dentro del rango [{min_val}, {max_val}], ejecutando {func.__name__}...") return func(*args, **kwargs) return envoltura return decorador # Uso del decorador @limitar_valores(0, 100) def multiplicar(a, b): return a * b # Llamadas válidas print(multiplicar(10, 5)) # 50 print(multiplicar(0, 100)) # 0 # Llamada inválida (lanza ValueError) print(multiplicar(150, 5)) # ValueError
def limitar_valores(min_val, max_val): """Decorador que asegura que todos los argumentos numéricos estén entre min_val y max_val."""
Esta es la función externa del decorador parametrizado.
Recibe dos parámetros: min_val y max_val, que definen el rango permitido para los argumentos de la función decorada.
Devuelve el decorador real que se aplicará a la función.
Decorador interno
def decorador(func):
Esta función interna recibe la función que vamos a decorar (func).
Su tarea es envolver a func con lógica adicional (en este caso, verificar que los valores estén en rango).
Devuelve la función envoltura.
Función envoltura
def envoltura(*args, **kwargs):
Esta es la función que reemplaza a la función original cuando usamos el decorador.
Recibe cualquier número de argumentos posicionales (*args) y de palabras clave (**kwargs).
Dentro de envoltura se añade la lógica de validación:
for i, arg in enumerate(args): if not (arg >=min_val and arg <= max_val): raise ValueError(f"Argumento {arg} en posición {i} fuera del rango [{min_val}, {max_val}]")
Recorre cada argumento posicional (args).
Si algún argumento está fuera del rango [min_val, max_val], lanza un ValueError.
Esto asegura que la función solo se ejecute con valores válidos.
Luego imprime un mensaje de confirmación:
print(f"Todos los argumentos dentro del rango [{min_val}, {max_val}], ejecutando {func.__name__}...")
Y finalmente ejecuta la función original:
return func(*args, **kwargs)
Creación una función que reciba como parámetro el nombre del jugador, luego tira dos dados y muestra el mensaje que gano si suma 7 y perdió en caso contrario. Crear una función decoradora que grabe en un archivo que se le pasa a la función decoradora el resultado de la tirada de los dos dados.
import random def guardar_en_archivo(nombre_archivo): """Decorador que guarda la salida de la función en un archivo de texto con jugador, dados y resultado.""" def decorador(func): def envoltura(*args, **kwargs): dado1, dado2 = func(*args, **kwargs) total = dado1 + dado2 if total == 7: resultado = "GANO" else: resultado = "PERDIO" with open(nombre_archivo, "a") as f: # Guardar nombre del jugador, valores de los dados y resultado f.write(f"{args[0]} {dado1} {dado2} {resultado}\n") print(f"{args[0]} {resultado} - Resultado guardado en {nombre_archivo}") return dado1, dado2, resultado return envoltura return decorador # Función más interesante: lanzar dos dados @guardar_en_archivo("partidas.txt") def jugar_dados(jugador): """Simula lanzar 2 dados y devuelve los valores de cada dado.""" dado1 = random.randint(1, 6) dado2 = random.randint(1, 6) print(f"{jugador} lanzó los dados: {dado1} + {dado2} = {dado1 + dado2}") return dado1, dado2 # Llamadas de ejemplo jugar_dados("Diego") jugar_dados("Carlos")
Definición del decorador parametrizado
def guardar_en_archivo(nombre_archivo): """Decorador que guarda la salida de la función en un archivo de texto con jugador, dados y resultado."""
Este decorador acepta un parámetro, nombre_archivo, que indica dónde se guardarán los resultados.
Es un decorador parametrizado, es decir, es una función que devuelve un decorador.
Decorador interno
def decorador(func):
Esta función recibe la función que será decorada (func).
Su objetivo es envolver la función original con comportamiento extra (guardar en archivo y calcular si ganó o perdió).
Función envoltura
def envoltura(*args, **kwargs):
Esta es la función que reemplaza a la original cuando la llamamos.
Recibe todos los argumentos posicionales (*args) y nombreados (**kwargs).
Llamar a la función original y calcular resultado
dado1, dado2 = func(*args, **kwargs) total = dado1 + dado2 if total == 7: resultado = "GANO" else: resultado = "PERDIO"
Se llama a la función original (jugar_dados) y se obtienen los valores de los dados.
Se calcula la suma (total).
Se determina si el jugador ganó (total == 7) o perdió (total != 7).
Guardar en archivo
with open(nombre_archivo, "a") as f: f.write(f"{args[0]} {dado1} {dado2} {resultado}\n")
Se abre el archivo en modo append ("a"), para no borrar lo anterior.
Se escribe una línea con el nombre del jugador (args[0]), los valores de los dados y el resultado.
Ejemplo de línea: Diego 4 2 PERDIO
Imprimir mensaje de confirmación y devolver resultado
print(f"{args[0]} {resultado} - Resultado guardado en {nombre_archivo}") return dado1, dado2, resultado
Se muestra un mensaje en pantalla confirmando que se guardó el resultado.
La función decorada devuelve los valores de los dados y el resultado, por si se quiere usar después en el programa.
Función que simula lanzar los dados
@guardar_en_archivo("partidas.txt") def jugar_dados(jugador): dado1 = random.randint(1, 6) dado2 = random.randint(1, 6) print(f"{jugador} lanzó los dados: {dado1} + {dado2} = {dado1 + dado2}") return dado1, dado2
Se usa el decorador @guardar_en_archivo("partidas.txt").
jugar_dados devuelve los valores de los dos dados.
También imprime en pantalla la tirada de cada jugador.
Llamadas de ejemplo
jugar_dados("Diego") jugar_dados("Carlos")
Cada llamada ejecuta envoltura, que:
Llama a jugar_dados. Calcula si ganó o perdió. Guarda la información en partidas.txt. Imprime un mensaje de confirmación.
Ejemplo de contenido de partidas.txt después de dos partidas
Diego 2 3 PERDIO Carlos 6 1 GANO
Cada línea corresponde a una tirada, mostrando jugador, valores de los dados y resultado.