annotationlib en Python: lee anotaciones

Publicado el: 14/08/2026
Tempo de leitura: 5 minutos
Código con anotaciones de tipos que representa introspección con annotationlib en Python

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:

  • VALUE evalúa expresiones y devuelve objetos reales.
  • FORWARDREF devuelve valores cuando es posible y proxies para nombres no resueltos.
  • STRING produce 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 Usuario

La 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 ModeloFuturo

Con 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 usar eval().
  • STRING no 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 FORWARDREF durante creación de clases.
  • Usa STRING para 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Archivadores organizados que representan módulos importados directamente desde archivos ZIP con zipimport en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipimport en Python: importa desde ZIP

    Aprende zipimport en Python para cargar módulos y paquetes desde archivos ZIP, usar importadores y proteger sistemas de plugins.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Diagrama de directorios que representa rutas site-packages y configuración del módulo site en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    site en Python: entiende las rutas

    Aprende el módulo site en Python para entender site-packages, user site, archivos .pth, sitecustomize, usercustomize y opciones de inicio.

    Ler mais

    Tempo de leitura: 7 minutos
    14/08/2026
    Icono de instalador que representa el bootstrap offline de pip con ensurepip en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ensurepip en Python: restaura pip

    Aprende ensurepip en Python para instalar o restaurar pip sin internet, elegir entorno, scripts, upgrade y evitar conflictos con el

    Ler mais

    Tempo de leitura: 6 minutos
    14/08/2026
    Paquete de software que representa metadatos consultados con importlib.metadata en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    importlib.metadata en Python: paquetes

    Aprende importlib.metadata en Python para consultar versiones, dependencias, archivos, metadatos y entry points de paquetes instalados.

    Ler mais

    Tempo de leitura: 6 minutos
    14/08/2026
    Código en ejecución que representa módulos y rutas ejecutados con runpy en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    runpy en Python: ejecuta módulos

    Aprende runpy en Python para ejecutar módulos, scripts, directorios y archivos ZIP, controlar namespaces y evitar problemas de seguridad y

    Ler mais

    Tempo de leitura: 6 minutos
    13/08/2026
    Paquete de software que representa descubrimiento de módulos con pkgutil en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pkgutil en Python: descubre paquetes

    Aprende pkgutil en Python para descubrir módulos, recorrer paquetes, resolver objetos, extender rutas y acceder a recursos con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026