Introducción
En este laboratorio, aprenderá sobre los Decoradores de Python, una característica potente que proporciona una forma concisa y reutilizable de extender la funcionalidad de una función o una clase sin modificarla permanentemente.
Comenzará construyendo un decorador simple, luego aprenderá a manejar parámetros de funciones, aplicará las mejores prácticas usando functools.wraps, creará decoradores que aceptan sus propios argumentos y, finalmente, implementará decoradores usando clases para gestionar el estado.
Decorador básico (estructura mínima)
En este paso, aprenderás la estructura fundamental de un decorador en Python. Un decorador, en su forma más simple, es una función que toma otra función como argumento, le añade alguna funcionalidad y luego devuelve otra función.
Nota: Si no estás familiarizado con el uso de WebIDE (similar a VS Code), por favor, apréndelo primero en este laboratorio.
Primero, abre el archivo step1.py desde el explorador de archivos a la izquierda.
Ahora, copia el siguiente código en el archivo step1.py. Este código define un decorador simple y lo aplica a una función.
## Decorator: a function that takes another function and returns a wrapper
def your_decorator(func):
def wrapper():
## Do stuff before func...
print("Before func!")
func() ## Call the original function
## Do stuff after func...
print("After func!")
return wrapper ## Return the wrapper function
## @your_decorator is syntactic sugar for: foo = your_decorator(foo)
@your_decorator
def foo():
print("Hello World!")
foo() ## Calls wrapper, which calls foo with extra behavior
Analicemos el código:
your_decorator(func): Esta es la función decoradora. Toma un argumento,func, que es la función a decorar.wrapper(): Esta es una función interna que "envuelve" (wraps) la función original. Contiene el nuevo comportamiento que deseas añadir. Aquí, imprime mensajes antes y después de llamar a la función original.@your_decorator: Este es el azúcar sintáctico (syntactic sugar) para aplicar el decorador. Es equivalente a escribirfoo = your_decorator(foo)después de la definición de la función.
Ahora, ejecuta el script en la terminal para ver el decorador en acción.
python3 step1.py
Deberías ver la siguiente salida, que muestra que la función wrapper fue ejecutada, añadiendo comportamiento alrededor de la función original foo.
Before func!
Hello World!
After func!
Decorador para una función con parámetros
En este paso, extenderá su decorador para que funcione con funciones que aceptan argumentos. Una función del mundo real a menudo tiene parámetros, y nuestro decorador debe ser capaz de manejarlos correctamente.
Para hacer esto, puede usar *args y **kwargs en la firma de la función wrapper. Esto permite que el wrapper acepte cualquier número de argumentos posicionales y de palabra clave (keyword arguments) y los pase a la función original.
Abra el archivo step2.py y añada el siguiente código:
## Decorator that works with functions that have parameters
def your_decorator(func):
def wrapper(*args,**kwargs): ## Accept any arguments
## Do stuff before func...
print("Before func!")
func(*args,**kwargs) ## Pass arguments to original function
## Do stuff after func...
print("After func!")
return wrapper
@your_decorator
def foo(bar):
print("My name is " + bar)
foo("Jack") ## Arguments are passed through wrapper
En este código, la función wrapper se define con *args y **kwargs. Cuando se llama a foo("Jack"), el argumento "Jack" es capturado por *args en el wrapper y luego pasado a la func original cuando se ejecuta func(*args, **kwargs).
Ejecute el script para ver el resultado.
python3 step2.py
La salida muestra que el argumento se pasó con éxito a través del decorador a la función original.
Before func!
My name is Jack
After func!
Plantilla para un decorador básico
En este paso, descubrirá la plantilla de mejores prácticas para crear decoradores robustos. Aunque los decoradores anteriores funcionan, tienen un efecto secundario: pierden los metadatos de la función original (como su nombre __name__ y su docstring __doc__).
Para resolver esto, el módulo functools de Python proporciona un decorador auxiliar llamado wraps. Además, un buen decorador debe ser capaz de manejar funciones que devuelven valores.
Esta plantilla es útil para la mayoría de los casos de uso de decoradores. Es válida para funciones con o sin parámetros, y con o sin valor de retorno.
Abra el archivo step3.py y añada el siguiente código.
import functools
## Best practice decorator template: preserves function metadata and return value
def your_decorator(func):
@functools.wraps(func) ## Preserves function name, docstring, etc.
def wrapper(*args,**kwargs):
print("Before function execution...")
result = func(*args,**kwargs) ## Call function and capture return value
print("After function execution...")
return result ## Return the original function's return value
return wrapper
@your_decorator
def add(a, b):
"""This function adds two numbers."""
return a + b
sum_result = add(5, 3)
print(f"Function name: {add.__name__}")
print(f"Docstring: {add.__doc__}")
print(f"Result: {sum_result}")
Mejoras clave en esta plantilla:
import functools: Importa el módulo necesario.@functools.wraps(func): Este es un decorador aplicado a la funciónwrapper. Copia los metadatos de lafuncoriginal (como__name__y__doc__) a la funciónwrapper.result = func(*args, **kwargs): El valor de retorno de la función original se captura en la variableresult.return result: La función wrapper devuelve el resultado capturado, por lo que la función decorada se comporta como se espera.
Ejecute el script:
python3 step3.py
La salida demuestra que el nombre y el docstring de la función se conservan, y el valor de retorno se maneja correctamente.
Before function execution...
After function execution...
Function name: add
Docstring: This function adds two numbers.
Result: 8
Decorador con parámetros
En este paso, aprenderá a crear decoradores que se pueden personalizar con sus propios parámetros. Esto permite un uso del decorador más flexible y configurable, como @your_decorator(arg='value').
Para lograr esto, necesita crear una "fábrica de decoradores" (decorator factory): una función que toma argumentos y devuelve un decorador. Esto añade una capa extra de anidamiento a nuestra estructura.
Crearemos un decorador que toma una cadena de texto prefix y la imprime antes y después de la llamada a la función. Abra step4.py y añada el siguiente código:
import functools
## Decorator factory: returns a decorator based on parameters
def prefix_decorator(prefix):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
## Use the 'prefix' argument from the factory
print(f"{prefix} Before function call")
result = func(*args, **kwargs)
print(f"{prefix} After function call")
return result
return wrapper
return decorator ## Return the actual decorator function
## Using the decorator with a parameter
@prefix_decorator(prefix="LOG:")
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
Así es como funciona:
- Primero se llama a
prefix_decorator(prefix="LOG:"). Esta es la función fábrica. - Devuelve la función
decorator, que ahora tiene acceso a la variableprefix("LOG:") a través de un closure (clausura). - Este
decoratordevuelto se aplica luego asay_hello, decorándola.
Ejecute el script para ver cómo se utiliza el parámetro.
python3 step4.py
La salida muestra el prefijo "LOG:", que se pasó como argumento al decorador.
LOG: Before function call
Hello, Alice!
LOG: After function call
Decoradores basados en clases
En este paso, explorará cómo implementar decoradores usando clases. Este enfoque es particularmente útil para mantener el estado (state) entre llamadas a funciones. Cubriremos dos patrones: decorar un método dentro de una clase y definir un decorador como una clase en sí misma.
Abra el archivo step5.py para comenzar.
Patrón 1: Decorar un método de clase
Puede definir un decorador como un método dentro de una clase y aplicarlo a otros métodos de esa misma clase. Esto es útil para crear comportamientos específicos ligados al contexto de la clase.
Añada el siguiente código a step5.py. Este ejemplo define un decorador para manejar excepciones en un método de clase.
## Pattern 1: Decorating a method within a class
class DecorateMyMethod:
## Static method decorator for methods with only 'self' parameter
def decorator_for_class_method_with_no_args(method):
def wrapper_for_class_method(self): ## Only takes self
try:
return method(self) ## Call original method
except Exception as e:
print(f"\nWARNING: An exception occurred: {e}\n")
return wrapper_for_class_method
def __init__(self,succeed:bool):
self.succeed = succeed
@decorator_for_class_method_with_no_args
def class_action(self):
if self.succeed:
print("You succeeded by choice.")
else:
raise Exception("Epic fail of your own creation.")
print("--- Testing Pattern 1 ---")
test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
test_fail = DecorateMyMethod(False)
test_fail.class_action()
print("-" * 25)
Patrón 2: Decorador como Clase
Un patrón más común y potente es definir el decorador en sí como una clase. Esto es ideal cuando el decorador necesita mantener un estado. Al implementar los métodos __init__() y __call__(), puede hacer que una instancia de una clase se comporte como una función.
Crearemos un decorador que cuenta cuántas veces se ha llamado a una función. Añada el siguiente código al final de step5.py.
## Pattern 2: Class-based decorator for maintaining state
class CountCallNumber:
def __init__(self, func):
self.func = func ## Store the function to decorate
self.call_number = 0 ## Initialize call counter
def __call__(self, *args, **kwargs): ## Makes instance callable
self.call_number += 1 ## Increment counter
print("This is execution number " + str(self.call_number))
return self.func(*args, **kwargs) ## Call original function
@CountCallNumber
def say_hi(name):
print("Hi! My name is " + name)
print("\n--- Testing Pattern 2 ---")
say_hi("Jack")
say_hi("James")
__init__(self, func): Este es el constructor. Se llama cuando se aplica el decorador (@CountCallNumber). Recibe la función a decorar (say_hi) y la almacena.__call__(self, *args, **kwargs): Este método hace que la instancia de la clase sea invocable (callable). Cuando llama a la función decorada (say_hi("Jack")), en realidad está invocando este método__call__. Incrementa el contador y luego llama a la función original almacenada.
Ahora, ejecute el script completo.
python3 step5.py
Verá la salida para ambos patrones. Observe cómo se incrementa el contador de llamadas en la segunda parte.
--- Testing Pattern 1 ---
You succeeded by choice.
WARNING: An exception occurred: Epic fail of your own creation.
-------------------------
--- Testing Pattern 2 ---
This is execution number 1
Hi! My name is Jack
This is execution number 2
Hi! My name is James
Resumen
¡Felicidades por completar el laboratorio! Ha adquirido una comprensión sólida de los decoradores de Python y sus diversas formas.
En este laboratorio, aprendió a:
- Crear un decorador básico y comprender la sintaxis
@. - Diseñar decoradores que funcionan con funciones que aceptan cualquier número de argumentos usando
*argsy**kwargs. - Utilizar la plantilla de mejores prácticas con
functools.wrapspara preservar los metadatos de la función y manejar los valores de retorno. - Crear fábricas de decoradores (decorator factories) que permiten personalizar sus decoradores con parámetros.
- Implementar decoradores como clases para mantener el estado (state) entre llamadas a funciones, como en un contador de llamadas.
Los decoradores son una característica clave para escribir código Python limpio, reutilizable y extensible. Ahora puede aplicar estos patrones para mejorar sus propios proyectos.



