Cómo usar decoradores para modificar el comportamiento de las funciones en Python

PythonPythonBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Los decoradores de Python son una herramienta poderosa que te permite modificar el comportamiento de las funciones sin cambiar su funcionalidad central. En este tutorial, adentraremos en el mundo de los decoradores y exploraremos cómo puedes utilizarlos para mejorar el rendimiento, el registro (logging) y la funcionalidad general de tu código Python.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") python/AdvancedTopicsGroup -.-> python/context_managers("Context Managers") subgraph Lab Skills python/function_definition -.-> lab-415777{{"Cómo usar decoradores para modificar el comportamiento de las funciones en Python"}} python/arguments_return -.-> lab-415777{{"Cómo usar decoradores para modificar el comportamiento de las funciones en Python"}} python/lambda_functions -.-> lab-415777{{"Cómo usar decoradores para modificar el comportamiento de las funciones en Python"}} python/decorators -.-> lab-415777{{"Cómo usar decoradores para modificar el comportamiento de las funciones en Python"}} python/context_managers -.-> lab-415777{{"Cómo usar decoradores para modificar el comportamiento de las funciones en Python"}} end

Comprendiendo los Decoradores de Python

Los decoradores de Python son una característica poderosa que te permite modificar el comportamiento de una función sin cambiar su código fuente. Son una forma de envolver una función con otra función, agregando funcionalidad adicional a la función original.

¿Qué son los Decoradores?

Los decoradores son una forma de modificar el comportamiento de una función o clase. Se definen utilizando el símbolo @, seguido de la función decoradora, y se colocan justo antes de la definición de la función o clase.

Aquí tienes un ejemplo de una función decoradora simple:

def uppercase(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

@uppercase
def say_hello():
    return "hello"

print(say_hello())  ## Output: HELLO

En este ejemplo, la función decoradora uppercase toma una función func como argumento y devuelve una nueva función wrapper que llama a func y luego convierte el resultado a mayúsculas.

Cómo Funcionan los Decoradores

Los decoradores funcionan modificando el comportamiento de una función en tiempo de ejecución. Cuando aplicas un decorador a una función, la función original se reemplaza con el resultado de la función decoradora. Esto significa que cuando llamas a la función decorada, en realidad estás llamando a la función envolvente (wrapper) que devolvió el decorador.

El proceso de aplicación de un decorador se puede desglosar en los siguientes pasos:

  1. Se define la función decoradora, que toma una función como argumento y devuelve una nueva función.
  2. Se utiliza el símbolo @ para aplicar el decorador a una función.
  3. Cuando se llama a la función decorada, se ejecuta la función envolvente (wrapper) devuelta por el decorador en lugar de la función original.

Beneficios de Usar Decoradores

Los decoradores ofrecen varios beneficios:

  1. Reutilización de Código: Los decoradores te permiten reutilizar la misma funcionalidad en múltiples funciones, lo que hace que tu código sea más DRY (No te repitas).
  2. Separación de Responsabilidades: Los decoradores te ayudan a separar la funcionalidad central de una función de la funcionalidad adicional que deseas agregar, lo que hace que tu código sea más modular y fácil de mantener.
  3. Flexibilidad: Los decoradores se pueden agregar o eliminar fácilmente de una función, lo que te permite habilitar o deshabilitar ciertos comportamientos con facilidad.
  4. Legibilidad: Los decoradores hacen que tu código sea más legible y auto-documentado, ya que el nombre del decorador indica claramente la funcionalidad adicional que se está agregando a una función.

Casos de Uso Comunes de los Decoradores

Los decoradores se pueden utilizar en una variedad de escenarios, incluyendo:

  • Registro (Logging): Agregar funcionalidad de registro a una función.
  • Caché (Caching): Almacenar en caché los resultados de una función para mejorar el rendimiento.
  • Autenticación: Verificar si un usuario está autorizado para acceder a una función.
  • Tiempo de Ejecución: Medir el tiempo de ejecución de una función.
  • Manejo de Errores: Proporcionar un manejo de errores personalizado para una función.

En la siguiente sección, exploraremos cómo aplicar decoradores para modificar el comportamiento de las funciones en Python.

Aplicando Decoradores para Modificar el Comportamiento de las Funciones

Ahora que tenemos una comprensión básica de lo que son los decoradores y cómo funcionan, exploremos cómo utilizarlos para modificar el comportamiento de las funciones en Python.

Pasando Argumentos a los Decoradores

Los decoradores también pueden aceptar argumentos, lo que te permite personalizar su comportamiento. Aquí tienes un ejemplo de un decorador que toma un argumento para controlar la capitalización de la salida:

def case_converter(case):
    def decorator(func):
        def wrapper():
            result = func()
            if case == "upper":
                return result.upper()
            elif case == "lower":
                return result.lower()
            else:
                return result
        return wrapper
    return decorator

@case_converter("upper")
def say_hello():
    return "hello"

print(say_hello())  ## Output: HELLO

@case_converter("lower")
def say_goodbye():
    return "GOODBYE"

print(say_goodbye())  ## Output: goodbye

En este ejemplo, el decorador case_converter toma un argumento case que determina si la salida debe convertirse a mayúsculas o minúsculas.

Decorando Funciones con Argumentos

Los decoradores también se pueden utilizar para modificar el comportamiento de las funciones que toman argumentos. Aquí tienes un ejemplo:

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_function_call
def add_numbers(a, b):
    return a + b

print(add_numbers(2, 3))  ## Output:
## Calling add_numbers with args=(2, 3) and kwargs={}
## 5

En este ejemplo, el decorador log_function_call envuelve la función add_numbers y registra (log) la llamada a la función antes de ejecutar la función original.

Apilando Decoradores

Los decoradores también se pueden apilar, lo que te permite aplicar múltiples decoradores a una sola función. Aquí tienes un ejemplo:

def uppercase(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

def exclaim(func):
    def wrapper():
        result = func()
        return result + "!"
    return wrapper

@exclaim
@uppercase
def say_hello():
    return "hello"

print(say_hello())  ## Output: HELLO!

En este ejemplo, la función say_hello está decorada con los decoradores uppercase y exclaim. Los decoradores se aplican en el orden en que se enumeran, por lo que el decorador uppercase se aplica primero y el decorador exclaim se aplica segundo.

Al utilizar decoradores, puedes modificar fácilmente el comportamiento de tus funciones sin cambiar su funcionalidad central. Esto hace que tu código sea más modular, reutilizable y fácil de mantener.

Técnicas y Casos de Uso Avanzados de los Decoradores

Como has visto, los decoradores son una herramienta poderosa para modificar el comportamiento de las funciones en Python. En esta sección, exploraremos algunas técnicas y casos de uso más avanzados de los decoradores.

Decorando Clases

Los decoradores también se pueden utilizar para modificar el comportamiento de las clases. Aquí tienes un ejemplo de un decorador que agrega un método de registro (logging) a una clase:

def log_class_methods(cls):
    class LoggedClass(cls):
        def __getattribute__(self, attr):
            if callable(super(LoggedClass, self).__getattribute__(attr)):
                def logged_method(*args, **kwargs):
                    print(f"Calling method {attr}")
                    return super(LoggedClass, self).__getattribute__(attr)(*args, **kwargs)
                return logged_method
            return super(LoggedClass, self).__getattribute__(attr)
    return LoggedClass

@log_class_methods
class MyClass:
    def __init__(self, value):
        self.value = value

    def do_something(self):
        print(f"Doing something with value: {self.value}")

obj = MyClass(42)
obj.do_something()  ## Output: Calling method do_something
                   ## Doing something with value: 42

En este ejemplo, el decorador log_class_methods toma una clase como argumento y devuelve una nueva clase que envuelve todos los métodos de la clase original con una función de registro.

Decoradores con Estado

Los decoradores también pueden mantener un estado entre las llamadas a la función. Esto puede ser útil para el almacenamiento en caché (caching), la limitación de tasa (rate limiting) u otras operaciones con estado. Aquí tienes un ejemplo de un decorador que almacena en caché los resultados de una función:

def cache(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            print("Returning cached result")
            return cache[args]
        else:
            result = func(*args)
            cache[args] = result
            return result
    return wrapper

@cache
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  ## Output: Calculating fibonacci(10)
                     ## 55
print(fibonacci(10))  ## Output: Returning cached result
                     ## 55

En este ejemplo, el decorador cache mantiene un diccionario de los argumentos de la llamada a la función y sus resultados correspondientes. Cuando se llama a la función decorada, el decorador primero verifica si el resultado ya está almacenado en caché y, si es así, devuelve el resultado almacenado. De lo contrario, calcula el resultado y lo almacena en la caché para su uso futuro.

Fábricas de Decoradores

A veces, es posible que desees crear decoradores que se puedan configurar con argumentos. Esto se puede lograr utilizando una fábrica de decoradores, que es una función que devuelve un decorador. Aquí tienes un ejemplo:

def repeat(n):
    def decorator(func):
        def wrapper():
            result = ""
            for _ in range(n):
                result += func()
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    return "hello "

print(say_hello())  ## Output: hello hello hello

En este ejemplo, la función repeat es una fábrica de decoradores que toma un argumento n y devuelve un decorador que envuelve la función original, la llama n veces y concatena los resultados.

Estas técnicas avanzadas de decoradores demuestran la flexibilidad y el poder de los decoradores en Python. Al utilizar decoradores, puedes crear código reutilizable, modular y fácil de mantener que se puede extender y personalizar fácilmente para satisfacer tus necesidades.

Resumen

Al final de este tutorial, tendrás una sólida comprensión de los decoradores de Python y cómo aplicarlos de manera efectiva para modificar el comportamiento de las funciones. Aprenderás técnicas avanzadas y explorarás casos de uso del mundo real, lo que te permitirá escribir código Python más eficiente, legible y fácil de mantener.