PEP 593: Descubriendo Annotated en Python
Una guía práctica sobre el tipo Annotated introducido en Python 3.9 que permite enriquecer tus anotaciones de tipo con metadata personalizada.
Por qué deberías conocer PEP 593
Si llevas un tiempo trabajando con Python, probablemente ya conoces las anotaciones de tipo (type hints). Introducidas en PEP 484 y PEP 3107, nos permiten escribir código como este:
def saludar(nombre: str) -> str:
return f"Hola, {nombre}!"
Pero aquí viene la pregunta interesante: ¿qué pasa si quieres añadir información extra a esos tipos? Por ejemplo, ¿validar que un número está en un rango específico, o indicar que una lista tiene un máximo de elementos?
Ahí es donde entra PEP 593 y su estrella: Annotated.
Qué es Annotated
El PEP 593 introduce Annotated[T, metadata] en el módulo typing. Básicamente, te permite decorar cualquier tipo con información adicional sin romper la compatibilidad con herramientas de type checking como mypy o Pyre.
La sintaxis es simple:
from typing import Annotated
# Una lista con máximo 10 elementos
ListaLimitada = Annotated[list, {"max_len": 10}]
# Un entero entre 0 y 100
Porcentaje = Annotated[int, {"range": (0, 100)}]
La regla de oro: si una herramienta no conoce tu metadata, simplemente la ignora y trata el tipo como T. Esto significa que puedes usar Annotated sin miedo a romper nada.
Un ejemplo real: validando datos con Pydantic
Aunque Annotated fue diseñado para usarse con cualquier metadata, brilla especialmente cuando lo combinas con bibliotecas como Pydantic. Desde Pydantic v2, puedes usar Annotated para validaciones inline:
from typing import Annotated
from pydantic import Field, constr, conint
class Usuario:
nombre: Annotated[str, Field(min_length=3, max_length=50)]
edad: Annotated[int, Field(ge=0, le=120)]
email: Annotated[str, constr(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")]
intentos_login: Annotated[int, Field(ge=0, le=5)] = 0
# Este usuario es válido
usuario = Usuario(nombre="Ana", edad=30, email="ana@ejemplo.com")
# Este lanzará un error de validación
try:
usuario_invalido = Usuario(nombre="Yo", edad=150, email="invalid")
except Exception as e:
print(e) # Validation error for field edad
Lo fascinante es que mypy sigue reconociendo el tipo subyacente. Cuando analice el código de arriba, verá str, int, etc., ignorando las validaciones de Pydantic. Pero Pydantic, al ejecutarse, lee esa metadata y aplica las restricciones automáticamente.
Por qué esto es un cambio de juego
Antes de PEP 593, si querías añadir metadata a tus tipos tenías dos opciones:
- Crear nuevos tipos: subclases de
int,str, etc. Funciona, pero es tedioso y poluciona el namespace. - Usar decoradores o clases wrapper: acababas con código complejo y difícil de mantener.
Con Annotated, puedes enriquecer tus tipos sin esfuerzo. Imagina un sistema de inventario donde los productos tienen restricciones:
from typing import Annotated
Cantidad = Annotated[int, {"min": 0, "max": 1000}]
Precio = Annotated[float, {"min": 0.0}]
class Producto:
nombre: str
cantidad: Cantidad
precio: Precio
Ahora cualquier biblioteca o herramienta que entienda estas restricciones puede usarlas. Y las que no, simplemente ven int o float.
Alias para evitar repetición
Si te parece verbose escribir Annotated everywhere, puedes crear alias:
from typing import Annotated, TypeVar
T = TypeVar("T")
# Alias genérico para valores positivos
Positive = Annotated[T, {"gt": 0}]
class Transaccion:
monto: Positive[float]
cantidad: Positive[int]
get_type_hints y el parámetro include_extras
Hay un detalle importante: typing.get_type_hints() por defecto ignora la metadata. Si necesitas acceder a ella, usa el parámetro include_extras=True:
from typing import Annotated, get_type_hints
class Persona:
nombre: Annotated[str, {"format": "uppercase"}]
# Sin extras (comportamiento por defecto)
get_type_hints(Persona) # {'nombre': str}
# Con extras
get_type_hints(Persona, include_extras=True) # {'nombre': Annotated[str, {'format': 'uppercase'}]}
Conclusión
PEP 593 resuelve un problema real: la necesidad de enriquecer tipos sin perder compatibilidad. Ya sea para validaciones con Pydantic, documentación adicional, o crear tus propias herramientas de análisis estático, Annotated es una herramienta que todo developer Python debería tener en su cinturón de utilidades.
La próxima vez que escribas type hints, pregúntate: ¿necesito añadir algo más a este tipo?
Te gusto este articulo?
Escribinos y conversemos sobre tu contexto.