El módulo annotationlib en Python, añadido en Python 3.14, ofrece herramientas de bajo nivel para recuperar y evaluar anotaciones de funciones, clases, módulos y objetos relacionados. Soporta evaluación diferida, referencias futuras sin resolver, metaclases y bibliotecas que necesitan información de tipos en runtime.
El módulo no es simplemente un lector seguro de diccionarios. Muchas operaciones pueden ejecutar expresiones contenidas en las anotaciones. Frameworks, plugins, generadores de documentación y validadores deben tratar la introspección como ejecución de código.
Por qué cambió el modelo
Hasta Python 3.13, las anotaciones normalmente se evaluaban cuando se definía una función o clase. Eso causaba errores con nombres declarados más tarde y aumentaba el trabajo durante imports. from __future__ import annotations almacenaba strings, pero complicaba la introspección.
Python 3.14 usa evaluación diferida por defecto. Las expresiones se calculan cuando se solicitan y annotationlib proporciona una interfaz coherente para varios formatos.
Usa get_annotations
get_annotations() es la API principal. Acepta callables, clases, módulos y otros objetos compatibles y devuelve un diccionario nuevo en cada llamada.
from annotationlib import get_annotations
def calcular(valor: int, escala: float) -> float:
return valor * escala
print(get_annotations(calcular))Prefiere esta función frente al acceso directo a __annotations__ porque maneja funciones diferidas, wrappers y comportamiento de clases.
Los tres formatos
La enumeración Format controla el resultado:
VALUEevalúa expresiones y devuelve objetos reales.FORWARDREFdevuelve valores cuando es posible y proxies para nombres no resueltos.STRINGproduce texto aproximado a la fuente.
from annotationlib import Format, get_annotations
def procesar(item: ModeloDefinidoDespues) -> list[str]:
...
referencias = get_annotations(
procesar,
format=Format.FORWARDREF,
)
textos = get_annotations(procesar, format=Format.STRING)Elige el formato según el consumidor, en lugar de evaluar todo automáticamente.
Formato VALUE
Cuando todos los nombres existen, VALUE devuelve clases, aliases y objetos de typing evaluados.
class Usuario:
pass
def guardar(usuario: Usuario) -> None:
pass
anotaciones = get_annotations(
guardar,
format=Format.VALUE,
)
assert anotaciones['usuario'] is UsuarioLa evaluación puede ejecutar código arbitrario incluido en la expresión. No uses este formato con anotaciones construidas desde entrada externa.
Formato FORWARDREF
Si un nombre no puede resolverse, el módulo devuelve un ForwardRef en lugar de lanzar NameError.
def cargar(valor: ModeloFuturo) -> ModeloFuturo:
pass
anotaciones = get_annotations(
cargar,
format=Format.FORWARDREF,
)
referencia = anotaciones['valor']Un proxy obtenido por get_annotations() puede conservar información del ámbito original.
Evalúa una referencia futura
ForwardRef.evaluate() intenta resolver la expresión. Proporciona un propietario o namespaces explícitos cuando sea necesario.
class ModeloFuturo:
pass
resuelto = referencia.evaluate(
globals=globals(),
locals=locals(),
)
assert resuelto is ModeloFuturoCon Format.FORWARDREF, partes todavía desconocidas pueden seguir como proxies. Con STRING, se obtiene texto.
Formato STRING
STRING intenta devolver una representación legible:
texto = get_annotations(
guardar,
format=Format.STRING,
)
print(texto)No reproduce exactamente el código. Comentarios, espacios, paréntesis, constantes y simplificaciones del compilador pueden cambiar. Algunas expresiones no están soportadas.
STRING no es sandbox
Solicitar texto puede ejecutar operaciones necesarias para reconstruir la expresión. Anotaciones diseñadas especialmente pueden llamar funciones o recorrer atributos incluso sin globals normales.
No aceptes texto de usuario, lo coloques en __annotations__ y lo pases a estas APIs. Genera documentación de código desconocido en un proceso aislado y, cuando sea posible, mediante AST.
eval_str para anotaciones antiguas
Objetos creados con from __future__ import annotations pueden almacenar strings. eval_str=True pide evaluar esos valores.
anotaciones = get_annotations(
objeto,
eval_str=True,
globals=espacio_global,
locals=espacio_local,
)Solo es compatible con Format.VALUE y posee los riesgos normales de eval().
Convierte valores a strings
annotations_to_string() convierte un mapeo de valores en textos de presentación.
from annotationlib import annotations_to_string
resultado = annotations_to_string({
'item': list[int],
'return': type(None),
})Es útil para funciones de anotación personalizadas que no conocen la fuente original.
Usa type_repr
type_repr() crea una representación orientada a tipos.
from annotationlib import type_repr
print(type_repr(dict[str, int]))No uses ese texto como identificador permanente ni formato de serialización.
Trabaja con __annotate__
Con evaluación diferida, el compilador puede crear un callable __annotate__. call_annotate_function() lo invoca en el formato solicitado.
from annotationlib import call_annotate_function, Format
valores = call_annotate_function(
MiClase.__annotate__,
Format.FORWARDREF,
owner=MiClase,
)La mayoría de bibliotecas debe usar get_annotations(). Estas APIs directas están orientadas a frameworks y metaclases.
Inspecciona durante la creación de clases
Una metaclase recibe el namespace antes de que exista la clase final. get_annotate_from_class_namespace() localiza la función diferida.
import annotationlib
class ModeloMeta(type):
def __new__(mcls, nombre, bases, namespace):
annotate = annotationlib.get_annotate_from_class_namespace(
namespace
)
if annotate:
campos = annotationlib.call_annotate_function(
annotate,
annotationlib.Format.FORWARDREF,
)
validar_campos(campos)
return super().__new__(mcls, nombre, bases, namespace)Las referencias futuras suelen ser preferibles porque la clase todavía no existe.
Evalúa aliases y parámetros
call_evaluate_function() trabaja con funciones diferidas de aliases y parámetros de tipo. Puede devolver valor, proxy o string.
valor = annotationlib.call_evaluate_function(
Alias.evaluate_value,
annotationlib.Format.FORWARDREF,
owner=Alias,
)Es una API avanzada para frameworks que trabajan con typing.
Integra con inspect
La guía de inspect en Python explica firmas y objetos vivos. Un framework puede combinar inspect.signature() con get_annotations() para elegir el formato adecuado.
Resolución de ámbitos
La función selecciona namespaces según el objeto. Los módulos usan su diccionario, las clases usan globals del módulo y namespace de clase, y los callables usan globals de la función después de desempaquetar wrappers conocidos.
Para entender nombres locales, globales, nonlocal, libres y parámetros de tipo, consulta symtable en Python.
Wrappers y partial
get_annotations() entiende funciones envueltas con functools.update_wrapper() y objetos partial. Los decoradores propios deben mantener __wrapped__.
Herencia de clases
Las anotaciones de clases no se heredan automáticamente y se ignoran las de la metaclase. Si una clase no posee anotaciones propias, el resultado es vacío. Los frameworks que combinan esquemas deben recorrer el MRO y definir precedencia.
La guía de types en Python presenta utilidades relacionadas con tipos de runtime.
Compatibilidad de versiones
annotationlib existe desde Python 3.14. Bibliotecas que soportan versiones anteriores necesitan imports condicionales o un backport apropiado de typing_extensions.
Seguridad
- Las anotaciones pueden ejecutar código arbitrario.
ForwardRef.evaluate()puede usareval().STRINGno garantiza ausencia de ejecución.- No proceses valores construidos con entrada externa.
- No expongas globals sensibles.
- Aplica límites de tiempo y memoria.
- Inspecciona plugins en otro proceso.
Pruebas recomendadas
Prueba nombres definidos después, aliases, genéricos, parámetros nativos, funciones envueltas, anotaciones futuras, nombres ausentes y expresiones con efectos secundarios. Ejecuta los tres formatos por separado.
No compares strings exactos en espacios y paréntesis; verifica contenido semántico.
Buenas prácticas
- Usa
get_annotations()como entrada principal. - Selecciona el formato explícitamente.
- Prefiere
FORWARDREFdurante creación de clases. - Usa
STRINGpara presentación. - Espera errores de la propia expresión.
- Mantén compatibilidad con versiones anteriores.
- No mezcles entrada de usuario con anotaciones.
- Documenta la política de evaluación.
Conclusión
annotationlib en Python proporciona la interfaz estándar para introspección de anotaciones diferidas en Python 3.14. Puede devolver objetos evaluados, proxies no resueltos o strings y ofrece herramientas avanzadas para metaclases y typing.
Su flexibilidad exige precaución porque la introspección puede ejecutar código. Consulta la documentación oficial de annotationlib, la PEP 649 y la PEP 749.







