Un decorador es una función que recibe otra función como argumento, le añade alguna funcionalidad extra y la devuelve modificada, todo sin alterar su código original. Es el pilar fundamental para frameworks como FastAPI o Flask.
def mi_decorador(funcion):
def envoltura():
print('1. Acceso antes de ejecutar la función.')
funcion()
print('2. Tarea posterior completada.')
return envoltura
@mi_decorador
def saludar():
print(' -> Hola Mundo <-')
saludar()
Permite ejecutar múltiples tareas de forma concurrente en un solo hilo. Mientras el programa espera una respuesta lenta (como una consulta a una base de datos o una descarga de internet), aprovecha ese tiempo muerto para ejecutar otra tarea.
import asyncio
async def descargar_datos(id_tarea):
print(f'Iniciando descarga {id_tarea}...')
await asyncio.sleep(2) # Simula una espera de red de 2 segundos
print(f'Descarga {id_tarea} finalizada.')
return f'Datos {id_tarea}'
async def main():
# Ejecuta ambas descargas al mismo tiempo de forma concurrente
resultados = await asyncio.gather(descargar_datos(1), descargar_datos(2))
print(resultados)
asyncio.run(main())
A diferencia de las listas que guardan todos sus elementos en la memoria RAM de golpe, los generadores van calculando y entregando los datos uno por uno, bajo demanda. Son indispensables para procesar archivos gigantescos de gigabytes de información sin saturar el sistema.
# Usa 'yield' en lugar de 'return' para crear un generador
def cuenta_regresiva(numero):
while numero > 0:
yield numero
numero -= 1
# No consume memoria hasta que se itera
for valor in cuenta_regresiva(1000000):
print(valor)
break # Detiene la ejecución consumiendo casi cero RAM
Seguramente has usado with open() para leer archivos. Puedes crear tus propios administradores de contexto usando los métodos mágicos __enter__ y __exit__ para asegurar que recursos costosos (como conexiones a bases de datos o sockets) se cierren siempre correctamente, incluso si ocurre un error catastrófico.
class GestorConexion:
def __enter__(self):
print('Conectando a la Base de Datos...')
return 'CONEXION_ACTIVA'
def __exit__(self, exc_type, exc_val, exc_tb):
print('Cerrando la conexión de forma segura.')
with GestorConexion() as db:
print(f'Ejecutando consultas con: {db}')
En Python, las clases también son objetos. Una metaclase es la "clase de una clase"; es decir, define cómo se construyen las propias clases. Te permite modificar la estructura de tus objetos en tiempo de diseño, validar que tus programadores cumplan con ciertas reglas o inyectar código automáticamente.
class ControlCalidadMeta(type):
def __new__(cls,name,bases,dct):
# Obliga a que todas las clases hijas tengan un método obligatorio
if 'guardar' not in dct:
raise TypeError(f"Error: La clase '{name}' debe implementar el método 'guardar'")
return super().__new__(cls,name,bases,dct)
# Esto lanzará un TypeError inmediatamente al compilar porque le falta 'guardar'
class ModeloUsuario(metaclass=ControlCalidadMeta):
def inicializar(self):
pass
Son la magia oculta detrás de decoradores como @property. Un descriptor te permite personalizar completamente qué pasa cuando se lee (__get__), escribe (__set__) o borra (__delete__) un atributo de un objeto, permitiendo validaciones robustas y limpias.
| Descriptor | Descripcion |
|---|---|
| __get__(self, instance, owner) | Se activa al leer el atributo. |
| __set__(self, instance, value) | Se activa al escribir/modificar el atributo. |
| __delete__(self, instance) | Se activa al borrar el atributo (del). |
class NumeroPositivo:
"""Descriptor que asegura que un atributo sea numérico y mayor que cero."""
def __init__(self, nombre_atributo):
self.nombre_atributo = nombre_atributo
def __get__(self, instancia, clase_propietaria):
# Si se accede desde la clase (no desde un objeto), devolvemos el descriptor mismo
if instancia is None:
return self
# Retorna el valor guardado en el diccionario interno del objeto
return instancia.__dict__.get(self.nombre_atributo)
def __set__(self, instancia, valor):
# Validaciones estrictas
if not isinstance(valor, (int, float)):
raise TypeError(f"El campo '{self.nombre_atributo}' debe ser un número.")
if valor < 0:
raise ValueError(f"El campo '{self.nombre_atributo}' no puede ser negativo.")
# Si pasa las pruebas, se guarda en el objeto
print(f"[Descriptor] Asignando {valor} a '{self.nombre_atributo}'")
instancia.__dict__[self.nombre_atributo] = valor
# --- Aplicación del Descriptor en nuestra clase de negocio ---
class Articulo:
# Vinculamos los atributos al comportamiento del descriptor
precio = NumeroPositivo('precio')
stock = NumeroPositivo('stock')
def __init__(self,nombre,precio,stock):
self.nombre = nombre
self.precio = precio # Esto activa automáticamente __set__
self.stock = stock # Esto activa automáticamente __set__