get_origin y get_args: inspecciona tipos genéricos

Publicado el: 29/08/2026
Tempo de leitura: 6 minutos
Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.

Las bibliotecas que procesan anotaciones necesitan descubrir de qué objeto parte una expresión de tipo y qué argumentos contiene. En Python, typing.get_origin() y typing.get_args() son las herramientas públicas principales para esa introspección. Permiten analizar estructuras como list[int], dict[str, float], Annotated, Literal, uniones, alias y tipos genéricos sin depender de detalles internos frágiles.

Esta guía explica cómo interpretar orígenes y argumentos, construir validadores y generadores de schemas, manejar uniones y alias, conservar metadatos, reconocer límites y evitar errores comunes en frameworks que leen type hints en runtime.

El problema de la introspección de tipos

expresion = list[int]
print(expresion)

La representación es útil para una persona, pero comparar cadenas o acceder a atributos privados no es seguro. Los objetos internos de typing han cambiado entre versiones. Las funciones públicas ofrecen una interfaz soportada para separar el constructor de tipo de sus parámetros.

Primer ejemplo con get_origin

from typing import get_origin

expresion = list[int]
print(get_origin(expresion))  # <class 'list'>

El origin es el objeto base parametrizado. Para list[int], el origen es list. Para dict[str, int], es dict. En un tipo no parametrizado como int, normalmente se obtiene None.

Extraer argumentos con get_args

from typing import get_args

expresion = dict[str, list[int]]
print(get_args(expresion))
# (<class 'str'>, list[int])

get_args() devuelve una tupla con los parámetros. Esos argumentos pueden contener otros tipos parametrizados, por lo que las herramientas reales suelen recorrer la estructura de manera recursiva.

Inspector recursivo

from typing import get_args, get_origin

def describir(expresion: object, nivel: int = 0) -> None:
    sangria = "  " * nivel
    origen = get_origin(expresion)
    argumentos = get_args(expresion)
    print(f"{sangria}tipo={expresion!r}, origen={origen!r}")
    for argumento in argumentos:
        describir(argumento, nivel + 1)

describir(dict[str, list[int | None]])

Este patrón es la base de serializadores, validadores, generadores de documentación e inyección de dependencias. Un algoritmo de producción debe tratar hojas sin argumentos y protegerse de recursión infinita en alias recursivos.

Uniones modernas

from types import UnionType
from typing import Union, get_args, get_origin

expresion = int | str
print(get_origin(expresion))
print(get_args(expresion))

Según la sintaxis y la versión de Python, el origen de una unión puede relacionarse con types.UnionType o typing.Union. No asumas una sola representación. Soporta las formas relevantes para la versión mínima del proyecto y prueba todos los intérpretes compatibles.

Optional es una unión

expresion = str | None
argumentos = get_args(expresion)
acepta_none = type(None) in argumentos

Optional[T] representa una unión de T y None. Buscar la palabra “Optional” en una representación textual es frágil. Analiza los argumentos y busca NoneType. Esto importa en validadores de configuración y APIs que distinguen campo ausente de valor nulo.

Literal

from typing import Literal, get_args, get_origin

Modo = Literal["lectura", "escritura"]
print(get_origin(Modo))
print(get_args(Modo))

En Literal, los argumentos son valores y no necesariamente tipos. Una herramienta genérica no puede asumir que cada elemento devuelto por get_args() es una clase. La guía sobre Literal en Python explica el uso de valores exactos.

Annotated y metadatos

from typing import Annotated, get_args, get_origin

Edad = Annotated[int, "mínimo 0", "máximo 130"]
print(get_origin(Edad))
print(get_args(Edad))

Los argumentos de Annotated empiezan con el tipo base y continúan con los metadatos. Los frameworks deben conservar el orden y decidir qué entradas reconocen. No descartes metadatos desconocidos si otra capa puede utilizarlos. Consulta Annotated en Python.

Callable requiere tratamiento especial

from collections.abc import Callable
from typing import get_args

TipoFuncion = Callable[[int, str], bool]
print(get_args(TipoFuncion))

La estructura de argumentos de Callable merece lógica dedicada. La lista de parámetros puede aparecer agrupada, y las formas con ParamSpec o Concatenate son más complejas. No trates Callable como una colección genérica ordinaria.

TypeVar y parámetros no resueltos

from typing import TypeVar

T = TypeVar("T")

Un TypeVar puede aparecer dentro de los argumentos y representar un parámetro todavía no sustituido. Un framework debe decidir si conserva el símbolo, aplica un mapa de especialización, usa su límite o rechaza schemas incompletos. Las restricciones también pueden afectar la interpretación.

Alias explícitos

type Resultado[T] = T | Exception

Los alias modernos pueden conservar identidad propia en runtime. Según la tarea, conviene mantener su nombre público o expandir el valor subyacente. La guía sobre TypeAliasType en Python explica por qué expandir automáticamente puede perder significado del dominio.

get_origin no sustituye get_type_hints

get_origin() y get_args() analizan un objeto de tipo ya disponible. No resuelven referencias futuras almacenadas como strings, anotaciones diferidas ni nombres dependientes de un namespace. Para obtener anotaciones resueltas de funciones y clases, usa typing.get_type_hints() con namespaces controlados.

from typing import get_type_hints

hints = get_type_hints(mi_funcion, include_extras=True)

Usa include_extras=True para conservar Annotated, Required, NotRequired y otros calificadores. La guía de get_type_hints en Python profundiza en resolución y seguridad.

Validador simplificado

from typing import get_args, get_origin

def validar(valor: object, expresion: object) -> bool:
    origen = get_origin(expresion)
    argumentos = get_args(expresion)

    if origen is list:
        if not isinstance(valor, list):
            return False
        (tipo_item,) = argumentos
        return all(validar(item, tipo_item) for item in valor)

    if origen is dict:
        if not isinstance(valor, dict):
            return False
        tipo_clave, tipo_valor = argumentos
        return all(
            validar(clave, tipo_clave)
            and validar(item, tipo_valor)
            for clave, item in valor.items()
        )

    if origen is None and isinstance(expresion, type):
        return isinstance(valor, expresion)

    return False

El ejemplo demuestra la mecánica, pero no es un validador de producción. No cubre uniones, Literal, Annotated, TypedDict, Protocol, recursión, coerción ni mensajes detallados. Sí muestra cómo origin y args orientan el despacho.

TypedDict necesita una ruta propia

TypedDict es una clase especial de tipado, no un dict parametrizado común. Su estructura se obtiene de anotaciones y conjuntos de claves requeridas u opcionales. get_origin() no sustituye esa introspección. Consulta TypedDict en Python.

Protocol y runtime

Protocol también exige cuidado. Que un tipo tenga origen y argumentos no significa que pueda validarse con isinstance(). Los Protocol runtime-checkable realizan comprobaciones estructurales limitadas y no verifican firmas completas. No conviertas introspección estática en promesas que Python no garantiza.

Tipos no parametrizados

assert get_origin(int) is None
assert get_args(int) == ()

Una tupla vacía no indica necesariamente error. Puede representar un tipo simple, un alias no expandido u otra forma especial. El consumidor debe interpretar el contexto.

Orden y normalización

El orden de argumentos suele ser significativo, pero caches internos y normalización de uniones pueden producir objetos equivalentes con historias diferentes. No uses repr() como clave persistente. Crea una representación canónica y versionada si necesitas almacenar schemas.

Seguridad

Las funciones de introspección solo examinan objetos, pero suelen utilizarse después de get_type_hints(), que puede evaluar referencias. No proceses anotaciones de código no confiable sin aislamiento. Los sistemas de plugins deben limitar módulos y namespaces permitidos.

Compatibilidad entre versiones

Prueba el comportamiento en cada versión soportada. El sistema de tipos incorpora alias explícitos, genéricos variádicos, calificadores y mecanismos de evaluación nuevos. Prefiere APIs públicas, evita clases internas con nombres privados y centraliza la compatibilidad.

Errores comunes

  • Comparar reprs: las representaciones textuales no son contratos estables.
  • Suponer que args son tipos: Literal devuelve valores y Annotated incluye metadatos.
  • Ignorar alias: expandirlos siempre puede perder nombres públicos.
  • Tratar cada origen como clase: las formas especiales necesitan despacho propio.
  • Olvidar referencias futuras: resuelve hints antes del análisis cuando sea necesario.
  • Sobreprometer Protocol: la presencia de atributos no prueba firmas.

Arquitectura recomendada

Separa resolución, normalización y consumo. Primero obtén hints con namespaces controlados. Después normaliza origins, argumentos, alias y metadatos en un árbol intermedio. Finalmente usa ese árbol para validación, documentación o serialización. Esta separación reduce el acoplamiento con detalles de typing y facilita las pruebas.

Conclusión

typing.get_origin() y typing.get_args() son la base pública para desmontar tipos parametrizados. Permiten construir frameworks robustos, pero uniones, Literal, Annotated, Callable, alias, TypeVar, TypedDict y Protocol requieren tratamiento consciente.

La documentación oficial de get_origin y get_args define la API. Combínala con get_type_hints(), conserva metadatos y mantén una capa de compatibilidad probada en todas las versiones soportadas.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Striking image of a red-bellied python showcasing its vibrant scales in dramatic lighting.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    LiteralString en Python: cadenas confiables

    Aprende LiteralString en Python para restringir SQL, templates y comandos a cadenas confiables y reducir riesgos de inyección.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dataclass_transform en Python: clases generadas

    Aprende dataclass_transform en Python para tipar decorators, clases base y metaclases que generan campos, __init__ y métodos.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    TypeVarTuple en Python: genéricos variádicos

    Aprende TypeVarTuple en Python para conservar tuplas heterogéneas, modelar dimensiones y crear genéricos con parámetros variables.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    assert_type y reveal_type: prueba inferencia de tipos

    Aprende assert_type y reveal_type en Python para inspeccionar inferencia, probar APIs tipadas y evitar regresiones estáticas.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    get_type_hints en Python: lee anotaciones

    Aprende get_type_hints en Python para resolver referencias futuras, conservar Annotated e inspeccionar funciones y clases con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    runtime_checkable en Python: Protocol runtime

    Aprende runtime_checkable en Python para comprobar Protocol con isinstance, entender límites y diseñar contratos estructurales seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026