typing.get_type_hints() recupera anotaciones de funciones, métodos, clases y módulos en una forma más útil que leer directamente __annotations__. Puede resolver referencias futuras, combinar anotaciones heredadas y conservar metadatos de Annotated cuando se solicita.
Ese poder requiere cuidado porque la resolución depende de namespaces y puede evaluar expresiones. Esta guía cubre decoradores, validadores, schemas, inyección de dependencias, documentación, forward references, genéricos, errores, caché, imports circulares y seguridad.
__annotations__ básico
def sumar(a: int, b: int) -> int:
return a + b
print(sumar.__annotations__)El diccionario contiene las anotaciones declaradas, pero algunos valores pueden ser strings u objetos no resueltos según la versión y la configuración del módulo.
Usar get_type_hints
from typing import get_type_hints
hints = get_type_hints(sumar)
print(hints)El resultado mapea nombres de parámetros y la clave especial return a objetos de tipo resueltos cuando es posible.
Referencias futuras
class Usuario:
gerente: "Usuario | None"
print(Usuario.__annotations__)
print(get_type_hints(Usuario))El acceso directo puede mostrar una string. get_type_hints intenta resolver Usuario y construir la unión real.
Anotaciones diferidas
Cuando el módulo usa evaluación diferida, las expresiones permanecen sin evaluar. get_type_hints es una forma centralizada de resolverlas siempre que los nombres estén disponibles.
Namespaces globales y locales
get_type_hints(objeto, globalns=globales, localns=locales)Los namespaces explícitos ayudan con clases dinámicas, funciones anidadas y herramientas que inspeccionan objetos fuera de su módulo original. Proporciona mappings correctos y mínimos.
Fallos de resolución
class Pedido:
cliente: "Cliente"Si Cliente no está disponible, get_type_hints puede lanzar NameError. Las herramientas deben capturar el fallo, identificar la anotación y decidir si aceptan valores no resueltos.
Annotated se elimina por defecto
from typing import Annotated
def edad(valor: Annotated[int, "0 a 130"]) -> None:
...Sin opciones extra, el resultado puede contener solo int.
Conservar extras
hints = get_type_hints(edad, include_extras=True)include_extras=True conserva Annotated, Required, NotRequired y calificadores relacionados. Consulta Annotated en Python.
Inspeccionar Annotated
from typing import get_args, get_origin
anotacion = hints["valor"]
print(get_origin(anotacion))
print(get_args(anotacion))Usa las APIs de typing en lugar de analizar representaciones de texto. get_args() devuelve el tipo base y los metadatos.
Funciones decoradas
Los decoradores deben usar functools.wraps para conservar metadatos y __wrapped__. Un wrapper mal implementado puede ocultar las anotaciones.
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperClases y métodos
class Configuracion:
host: str
puerto: int
print(get_type_hints(Configuracion))Para clases, la función combina anotaciones a lo largo del MRO. Las definiciones derivadas tienen prioridad.
Herencia
class Base:
id: int
class Derivada(Base):
nombre: str
get_type_hints(Derivada)El resultado puede incluir id y nombre. Una anotación derivada sustituye a la base con el mismo nombre.
ClassVar y Final
Con extras preservados, un framework puede distinguir campos de instancia, variables de clase y declaraciones finales. Cada herramienta decide cómo documentarlos o ignorarlos.
TypedDict
get_type_hints recupera tipos de valores, mientras que la presencia de claves depende también de __required_keys__ y __optional_keys__. Un generador de schema debe combinar ambas fuentes.
Dataclasses
En dataclasses, get_type_hints resuelve tipos y dataclasses.fields() aporta defaults, factories, flags y metadatos. Los serializadores suelen necesitar las dos APIs.
Alias de tipo
Los alias modernos pueden mantenerse como entidades nombradas o evaluarse según la ruta de inspección. Decide cuándo expandir TypeAliasType y cuándo conservar su nombre. Consulta TypeAliasType en Python.
Genéricos
from typing import Generic, TypeVar
T = TypeVar("T")
class Caja(Generic[T]):
valor: Tget_type_hints recupera T, pero no sustituye automáticamente int en todos los contextos de Caja[int]. La especialización concreta requiere orígenes, argumentos y bases genéricas.
Objetos no soportados
No todo objeto expone anotaciones útiles. Built-ins y extensiones C pueden carecer de metadatos completos. Valida la entrada y produce mensajes claros.
Evaluación y seguridad
Las anotaciones pueden contener expresiones. Resolverlas puede ejecutar código en ciertos escenarios. No trates anotaciones de fuentes no confiables como datos inertes. Limita namespaces y evita importar módulos desconocidos solo para resolver hints.
No es un validador directo
get_type_hints describe el contrato declarado, pero no prueba valores. Un validador debe interpretar uniones, colecciones, TypedDict, Protocol, Annotated, recursión y metadatos personalizados.
Caché
Resolver repetidamente puede ser costoso. Los frameworks suelen guardar resultados por función o clase. Modificar anotaciones dinámicamente complica la invalidación y es mejor evitarlo.
Imports circulares
Las referencias futuras reducen imports superiores, pero get_type_hints necesita nombres durante la resolución. Organiza módulos, usa namespaces explícitos y retrasa la evaluación hasta la inicialización cuando sea necesario.
Imports con TYPE_CHECKING
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from paquete import ClienteEl import existe solo para análisis estático. En runtime, get_type_hints puede no encontrar Cliente. Proporciona el nombre mediante un import real o globalns.
Validador de llamada simple
from inspect import signature
from typing import get_type_hints
def validar_llamada(func, *args, **kwargs):
sig = signature(func)
ligados = sig.bind(*args, **kwargs)
hints = get_type_hints(func)
for nombre, valor in ligados.arguments.items():
esperado = hints.get(nombre)
if isinstance(esperado, type) and not isinstance(valor, esperado):
raise TypeError(f"{nombre} debe ser {esperado.__name__}")La demostración soporta clases simples. Uniones, genéricos, Protocol y estructuras anidadas exigen un motor completo.
Generación de documentación
Una herramienta puede combinar inspect.signature(), docstrings, defaults y get_type_hints. Conserva alias públicos y Annotated cuando mejoren el contrato generado.
Errores comunes
- Leer solo __annotations__: referencias pueden seguir siendo strings.
- Olvidar include_extras: metadatos y calificadores desaparecen.
- Ignorar NameError: algunos nombres no se pueden resolver.
- Evaluar código no confiable: las anotaciones no siempre son datos seguros.
- Tratar hints como validación: todavía hay que comprobar valores.
- Resolver en cada llamada: puede ser necesario cachear.
Ejemplo completo: registro de handlers
from typing import get_type_hints
class Evento: ...
class Contexto: ...
handlers: dict[type[Evento], object] = {}
def handler(func):
hints = get_type_hints(func, include_extras=True)
evento = hints.get("evento")
retorno = hints.get("return")
if not isinstance(evento, type) or not issubclass(evento, Evento):
raise TypeError("parámetro evento inválido")
if retorno is not None and retorno is not type(None):
raise TypeError("el handler debe devolver None")
handlers[evento] = func
return func
@handler
def procesar(evento: Evento, contexto: Contexto) -> None:
...El decorador usa las anotaciones como API intencional. Una implementación real también comprobaría nombres, cantidad de parámetros y mensajes de error.
Cuándo evitar introspección
Si la interfaz puede registrarse explícitamente, pasar argumentos suele ser más simple y predecible. Usa get_type_hints cuando las anotaciones sean parte intencional de un framework, como serialización, inyección, validación o documentación.
Conclusión
get_type_hints() es la herramienta principal para recuperar anotaciones resueltas en runtime. Maneja referencias futuras, herencia y extras mejor que __annotations__.
La documentación oficial de get_type_hints en Python describe sus parámetros. Usa namespaces controlados, conserva extras cuando sea necesario, maneja fallos, cachea responsablemente y nunca evalúes anotaciones no confiables sin considerar el riesgo de ejecución.







