Decoradores de Python

Un Decorador de Python proporciona una forma concisa y reutilizable de extender una función o una clase. Lea el artículo complementario Python Decorators: Simple Patterns to Level Up Your Code para ver ejemplos prácticos y patrones.

Decorador básico

Un decorador en su forma más simple es una función que toma otra función como argumento y devuelve una función envolvente (wrapper). El siguiente ejemplo muestra la creación de un decorador y su uso.

# Decorador: una función que toma otra función y devuelve un envoltorio
def your_decorator(func):
  def wrapper():
    # Hacer cosas antes de func...
    print("Before func!")
    func()  # Llama a la función original
    # Hacer cosas después de func...
    print("After func!")
  return wrapper  # Devuelve la función envolvente

# @your_decorator es azúcar sintáctico para: foo = your_decorator(foo)
@your_decorator
def foo():
  print("Hello World!")

foo()  # Llama a wrapper, que llama a foo con comportamiento extra
Before func!
Hello World!
After func!
Quiz

Inicia sesión para responder este quiz y rastrear tu progreso de aprendizaje

¿Qué es un decorador en Python?
A. Una función que toma otra función y devuelve una función envolvente
B. Un tipo especial de clase
C. Una palabra clave incorporada de Python
D. Una forma de eliminar funciones

Decorador para una función con parámetros

# Decorador que funciona con funciones que tienen parámetros
def your_decorator(func):
  def wrapper(*args,**kwargs):  # Acepta cualquier argumento
    # Hacer cosas antes de func...
    print("Before func!")
    func(*args,**kwargs)  # Pasa argumentos a la función original
    # Hacer cosas después de func...
    print("After func!")
  return wrapper

@your_decorator
def foo(bar):
  print("My name is " + bar)

foo("Jack")  # Los argumentos se pasan a través del envoltorio
Before func!
My name is Jack
After func!

Plantilla para un decorador básico

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.

import functools

# Plantilla de decorador de mejores prácticas: preserva los metadatos y el valor de retorno de la función
def your_decorator(func):
  @functools.wraps(func)  # Preserva el nombre de la función, la cadena de documentación, etc.
  def wrapper(*args,**kwargs):
    # Hacer cosas antes de func...
    result = func(*args,**kwargs)  # Llama a la función y captura el valor de retorno
    # Hacer cosas después de func..
    return result  # Devuelve el valor de retorno de la función original
  return wrapper
Quiz

Inicia sesión para responder este quiz y rastrear tu progreso de aprendizaje

¿Qué hace @functools.wraps(func) en un decorador?
A. Hace que el decorador se ejecute más rápido
B. Preserva los metadatos de la función original (nombre, cadena de documentación, etc.)
C. Evita que se llame a la función
D. Convierte la función en una clase

Decorador con parámetros

También puedes definir parámetros para que los use el decorador.

import functools

# Fábrica de decoradores: devuelve un decorador basado en parámetros
def your_decorator(arg):
  def decorator(func):
    @functools.wraps(func)  # Preservar metadatos de la función
    def wrapper(*args,**kwargs):
      # Hacer cosas antes de func posiblemente usando arg...
      result = func(*args,**kwargs)
      # Hacer cosas después de func posiblemente usando arg...
      return result
    return wrapper
  return decorator  # Devuelve la función decoradora real

Para usar este decorador:

# Uso del decorador con parámetros: @your_decorator(arg='x') llama a your_decorator('x')
# que devuelve un decorador que luego se aplica a foo
@your_decorator(arg = 'x')
def foo(bar):
  return bar

Decoradores basados en clases

Para decorar un método de clase, debes definir el decorador dentro de la clase. Cuando solo se pasa el argumento implícito self al método, sin argumentos adicionales explícitos, debes crear un decorador separado solo para esos métodos sin argumentos adicionales. Un ejemplo de esto, que se muestra a continuación, es cuando deseas capturar e imprimir excepciones de cierta manera.

# Decorador de método de clase: definido dentro de la clase
class DecorateMyMethod:

  # Decorador de método estático para métodos con solo el parámetro 'self'
  def decorator_for_class_method_with_no_args(method):
    def wrapper_for_class_method(self):  # Solo toma self
      try:
        return method(self)  # Llama al método original
      except Exception as e:
        print("\nWARNING: Please make note of the following:\n")
        print(e)
    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.")

test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
You succeeded by choice.
test_fail = DecorateMyMethod(False)
test_fail.class_action()
Exception: Epic fail of your own creation.

Un decorador también puede definirse como una clase en lugar de un método. Esto es útil para mantener y actualizar un estado, como en el siguiente ejemplo, donde contamos el número de veces que se llama a un método:

# Decorador basado en clases: mantiene el estado entre llamadas
class CountCallNumber:

  def __init__(self, func):
    self.func = func  # Almacena la función a decorar
    self.call_number = 0  # Inicializa el contador de llamadas

  def __call__(self, *args, **kwargs):  # Hace que la instancia sea invocable
    self.call_number += 1  # Incrementa el contador
    print("This is execution number " + str(self.call_number))
    return self.func(*args, **kwargs)  # Llama a la función original

@CountCallNumber  # Crea una instancia de CountCallNumber
def say_hi(name):
  print("Hi! My name is " + name)

say_hi("Jack")  # Llama a CountCallNumber.__call__()
This is execution number 1
Hi! My name is Jack
say_hi("James")
This is execution number 2
Hi! My name is James

Ejemplo de Conteo

Este ejemplo de conteo está inspirado en el tutorial de YouTube de Patrick Loeber.

Enlaces relevantes