Saltar al contenido
Conceptos Avanzados de Python

Decoradores y Metaprogramación

Introducción

Los decoradores y la metaprogramación son conceptos avanzados en Python que te permiten modificar el comportamiento de una función o de un objeto en tiempo de ejecución. Un decorador es una función que toma otra función y extiende o modifica su comportamiento sin modificar su propia definición. Los decoradores se escriben como funciones que toman otra función como argumento y devuelven una función modificada. Los decoradores son útiles cuando quieres agregar alguna funcionalidad adicional a una función existente sin modificar su definición.

Por otro lado, la metaprogramación es la capacidad de un programa para manipular su propio código en tiempo de ejecución. Esto significa que un programa escrito en Python puede crear, modificar o eliminar partes de sí mismo mientras se está ejecutando. Un ejemplo de metaprogramación es la creación de clases dinámicas y la definición de atributos y métodos en tiempo de ejecución. En definitiva, los decoradores y la metaprogramación son herramientas avanzadas que te permiten crear programas más flexibles y personalizables en Python.

Resumen

Los decoradores son una herramienta en Python que permiten modificar la funcionalidad de una función, método o clase sin tener que modificar directamente su código. En Python, una función es un objeto, por lo que se puede "decorar" agregando más funcionalidad a esa función en tiempo de ejecución mediante la adición de una función de decoración. Los decoradores se aplican utilizando el símbolo "@", seguido del nombre del decorador.

Ejemplo de decorador en Python

        
import time

# Define el decorador timer
def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)  # Llama a la función decorada con los argumentos proporcionados
        end_time = time.time()
        print("Tiempo de ejecución:", end_time - start_time, "segundos")
        return result
    return wrapper

# Aplica el decorador a la función
@timer
def my_function():
    time.sleep(2)

# Llama a la función decorada
my_function()
        
    

En este ejemplo, el decorador "timer" se define como una función que toma como argumento otra función. Dentro de "timer", se define otra función llamada "wrapper" que mide el tiempo de ejecución de la función original "my_function" y lo imprime. Luego, "wrapper" llama a la función original "my_function" y devuelve su resultado.

La metaprogramación se refiere a la capacidad del lenguaje de programación para crear, modificar o analizar código en tiempo de ejecución. En Python, esto se logra a través de la introspección y la capacidad de Python de tratar a las funciones y las clases como objetos.

Ejemplo de metaprogramación en Python

        
# Diccionario que define la nueva clase
class_dict = {
    "name": "MyClass",  # Nombre de la clase
    "attributes": {     # Atributos de la clase
        "foo": 123,     # Atributo foo con valor 123
        "bar": "abc",   # Atributo bar con valor "abc"
    },
}

# Función para crear la clase dinámicamente
def create_class(class_dict):
    # Utiliza la función type() para crear una nueva clase
    new_class = type(class_dict["name"], (), class_dict["attributes"])
    return new_class

# Crea la nueva clase utilizando el diccionario
MyClass = create_class(class_dict)

# Accede a los atributos de la clase
print(MyClass.foo)  # Imprime: 123
print(MyClass.bar)  # Imprime: "abc"
        
    

En este ejemplo, la función "create_class" toma un diccionario que describe una clase, utiliza el constructor de "type" para crear una nueva clase a partir del nombre y atributos proporcionados, y devuelve la clase recién creada. Luego, la variable "MyClass" se establece en el resultado de llamar a "create_class", lo que permite acceder a los atributos de "foo" y "bar" de la nueva clase.

Aplicación teórica

Un ejemplo práctico de meta programación con decoradores en Python podría ser la creación de un decorador que agregue una función básica de registro de errores a cualquier función. Este decorador podría seguir los siguientes pasos:

  1. Recibe la función original como entrada.
  2. Define una nueva función interna que envuelve la función original.
  3. La nueva función ejecuta la función original y captura cualquier excepción que se produzca.
  4. Si se produce una excepción, la nueva función escribe una entrada en un archivo de registro de errores.
  5. La nueva función devuelve el resultado de la función original.

Por ejemplo, podemos implementar un decorador para el registro de errores así:

        
def registro_errores(func):
    def wrapper(*args, **kwargs):
        try:
            # Ejecutar la función envuelta
            return func(*args, **kwargs)
        except Exception as e:
            # Capturar cualquier excepción y escribir el error en el archivo de registro
            with open('log.txt', 'a') as f:
                f.write(f'Error: {str(e)}\n')
            # Volver a lanzar la excepción para que el programa no ignore el error
            raise
    return wrapper
        
    

Podemos entonces aplicar este decorador a cualquier función que queramos registrar errores, como:

        
@registro_errores
def division(a, b):
    return a / b
        
    

Al llamar a esta función en el código, cualquier excepción que se produzca se escribirá en el archivo 'log.txt'. Este es un ejemplo práctico de metaprogramación con decoradores en Python.

Aplicación práctica

Un ejemplo práctico de decoradores en Python podría ser un decorador que mida el tiempo de ejecución de una función. El código se vería así:

        
import time

def tiempo_ejecucion(funcion):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = funcion(*args, **kwargs)
        final = time.time()
        print("Tiempo de ejecución: {} segundos".format(final - inicio))
        return resultado
    return wrapper

@tiempo_ejecucion
def funcion_ejemplo():
    time.sleep(2)

funcion_ejemplo()  # Salida: Tiempo de ejecución: 2.001251459121704 segundos
        
    

En este ejemplo, el decorador tiempo_ejecucion es una función que recibe como argumento otra función (funcion). El decorador devuelve una nueva función (wrapper) que envuelve la función original (funcion) y añade una funcionalidad adicional: en este caso, medir el tiempo de ejecución.