get_origin e get_args: inspecione tipos genéricos

Publicado em: 29/08/2026
Tempo de leitura: 6 minutos
Close-up view of a computer screen displaying code in a software development environment.

Bibliotecas que processam anotações precisam descobrir de que tipo uma expressão foi construída e quais argumentos ela contém. Em Python, typing.get_origin() e typing.get_args() são as ferramentas centrais para essa introspecção. Elas permitem analisar estruturas como list[int], dict[str, float], Annotated, Literal, uniões, aliases e tipos genéricos sem depender de detalhes internos frágeis.

Neste guia, você aprenderá a interpretar origins e argumentos, criar validadores e geradores de schema, lidar com uniões e aliases, preservar metadados, reconhecer limitações e evitar erros comuns em frameworks que leem type hints em runtime.

O problema da introspecção de tipos

tipo = list[int]
print(tipo)

A representação mostra informação útil, mas comparar strings ou acessar atributos privados é inseguro. A implementação interna de objetos de tipagem pode mudar entre versões. As funções públicas de typing fornecem uma interface estável para separar o construtor do tipo de seus parâmetros.

Primeiro exemplo com get_origin

from typing import get_origin

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

O origin é o objeto base que foi parametrizado. Para list[int], o origin é list. Para dict[str, int], é dict. Em tipos não parametrizados, como int, o resultado normalmente é None.

Extraindo argumentos com get_args

from typing import get_args

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

get_args() devolve uma tupla com os parâmetros de tipo. Os argumentos podem conter outros tipos parametrizados, portanto ferramentas reais geralmente percorrem a estrutura recursivamente.

Construindo um inspetor recursivo

from typing import get_args, get_origin

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

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

Esse padrão é a base de serializadores, validadores, geradores de documentação e ferramentas de injeção de dependência. O algoritmo precisa tratar folhas sem argumentos e evitar recursão infinita em aliases recursivos.

Uniões modernas

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

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

Dependendo da forma e da versão do Python, a origem de uma união pode estar relacionada a types.UnionType ou typing.Union. Em vez de assumir uma única representação, compare com as formas suportadas pela versão mínima do projeto e mantenha testes em todos os interpretadores compatíveis.

Optional é uma união

tipo = str | None
argumentos = get_args(tipo)
aceita_none = type(None) in argumentos

Optional[T] representa uma união entre T e None. Detectar apenas o nome “Optional” é frágil. Analise os argumentos e procure NoneType. Esse cuidado é importante em validadores de configuração e APIs que distinguem campo ausente de valor nulo.

Literal

from typing import Literal, get_args, get_origin

Modo = Literal["leitura", "escrita"]
print(get_origin(Modo))
print(get_args(Modo))

Em Literal, os argumentos são valores, não necessariamente tipos. Uma ferramenta genérica não pode presumir que tudo retornado por get_args() seja uma classe. O guia sobre Literal no Python mostra como valores exatos melhoram contratos e overloads.

Annotated e metadados

from typing import Annotated, get_args, get_origin

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

Os argumentos de Annotated começam pelo tipo base e continuam com os metadados. Frameworks devem preservar a ordem e decidir quais metadados reconhecem. Não descarte informações desconhecidas silenciosamente quando outra camada puder utilizá-las. Consulte também Annotated no Python.

Callable

from collections.abc import Callable
from typing import get_args

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

A estrutura dos argumentos de Callable merece tratamento específico. A lista de parâmetros pode aparecer agrupada, e formas com ParamSpec ou Concatenate são mais complexas. Não trate Callable como uma coleção genérica comum.

TypeVar e parâmetros genéricos

from typing import TypeVar

T = TypeVar("T")

Um TypeVar pode surgir dentro dos argumentos e representar uma variável ainda não substituída. Ferramentas devem decidir se mantêm o parâmetro simbólico, aplicam um mapeamento de especialização ou rejeitam schemas incompletos. Limites e restrições de TypeVar também podem influenciar a interpretação.

Aliases explícitos

type Resultado[T] = T | Exception

Aliases modernos podem preservar identidade própria em runtime. Dependendo da tarefa, você pode querer manter o nome público do alias ou expandir seu valor. O artigo sobre TypeAliasType no Python explica como aliases explícitos se comportam e por que expansão automática pode perder semântica.

get_origin não substitui get_type_hints

get_origin() e get_args() analisam um objeto de tipo já disponível. Eles não resolvem automaticamente referências futuras em strings, namespaces ou anotações adiadas. Para obter anotações resolvidas de funções e classes, use typing.get_type_hints() com os namespaces apropriados.

from typing import get_type_hints

hints = get_type_hints(minha_funcao, include_extras=True)

Use include_extras=True quando precisar preservar Annotated, Required, NotRequired e outros qualificadores. O guia sobre get_type_hints no Python aprofunda resolução e segurança.

Exemplo: validador simplificado

from typing import get_args, get_origin

def validar(valor: object, tipo: object) -> bool:
    origem = get_origin(tipo)
    argumentos = get_args(tipo)

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

    if origem is dict:
        if not isinstance(valor, dict):
            return False
        tipo_chave, tipo_valor = argumentos
        return all(
            validar(chave, tipo_chave)
            and validar(item, tipo_valor)
            for chave, item in valor.items()
        )

    if origem is None and isinstance(tipo, type):
        return isinstance(valor, tipo)

    return False

O exemplo demonstra a mecânica, mas não é um validador de produção. Ele não cobre uniões, Literal, Annotated, TypedDict, Protocol, recursão, coerção, mensagens detalhadas nem tipos especiais. Ainda assim, mostra como origin e args orientam o despacho.

TypedDict

TypedDict é uma classe especial de tipagem, não um dict parametrizado comum. Sua estrutura é encontrada em __annotations__, __required_keys__ e __optional_keys__. get_origin() não substitui essa introspecção específica. O guia sobre TypedDict no Python explica esses contratos.

Protocol

Protocol também exige tratamento especializado. O fato de um objeto possuir origin e argumentos não prova que ele possa ser validado com isinstance(). Protocols runtime-checkable verificam apenas presença estrutural limitada, não assinaturas completas. Evite transformar introspecção estática em promessas de runtime que o Python não garante.

Tipos não parametrizados

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

Uma tupla vazia de argumentos não significa erro. Ela pode indicar um tipo simples, um alias não expandido ou uma forma especial. O chamador deve interpretar o contexto.

Ordem e normalização

A ordem dos argumentos costuma ser significativa, mas caches internos e normalização de uniões podem alterar representações equivalentes. Não use repr() como chave persistente de schema. Crie uma representação canônica própria e versionada quando precisar armazenar resultados.

Segurança

As funções de introspecção em si apenas examinam objetos, mas normalmente são usadas depois de get_type_hints(), que pode avaliar referências. Não processe anotações de código não confiável sem isolamento. Bibliotecas de plugins devem definir claramente quais módulos e namespaces podem ser carregados.

Compatibilidade

Teste o comportamento em cada versão suportada do Python. A evolução do sistema de tipos introduz novas formas, como aliases explícitos, genéricos variádicos e qualificadores adicionais. Prefira APIs públicas, evite classes internas com nomes iniciados por sublinhado e mantenha uma camada de compatibilidade centralizada.

Erros comuns

  • Comparar reprs: representações textuais não são contratos estáveis.
  • Assumir que args são tipos: Literal devolve valores e Annotated inclui metadados.
  • Ignorar aliases: expandi-los sempre pode perder nomes públicos.
  • Tratar toda origem como classe: formas especiais exigem despacho próprio.
  • Esquecer referências futuras: resolva hints antes da análise quando necessário.
  • Validar Protocol superficialmente: presença de atributos não garante assinatura.

Arquitetura recomendada

Separe resolução, normalização e consumo. Primeiro obtenha hints com namespaces controlados. Depois normalize origins, args, aliases e metadados em uma árvore intermediária. Por fim, use essa árvore para validação, documentação ou serialização. Essa separação reduz acoplamento com detalhes de typing e facilita testes.

Conclusão

typing.get_origin() e typing.get_args() fornecem a base pública para desmontar tipos parametrizados. Elas ajudam a construir frameworks robustos, mas exigem tratamento consciente para uniões, Literal, Annotated, Callable, aliases, TypeVar, TypedDict e Protocol.

A documentação oficial de get_origin e get_args descreve a API. Use-a junto de get_type_hints(), preserve metadados e mantenha uma camada de compatibilidade testada em todas as versões suportadas.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    LiteralString no Python: strings confiáveis

    Aprenda LiteralString no Python para restringir SQL, templates e comandos a strings confiáveis e reduzir injeções com análise estática.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    dataclass_transform no Python: classes geradas

    Aprenda dataclass_transform no Python para tipar decorators, metaclasses e frameworks que geram __init__, campos e métodos.

    Ler mais

    Tempo de leitura: 6 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 Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeVarTuple no Python: genéricos variádicos

    Aprenda TypeVarTuple no Python para preservar tuplas heterogêneas, modelar dimensões e criar genéricos com vários parâmetros.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    assert_type e reveal_type: teste inferência de tipos

    Aprenda assert_type e reveal_type no Python para inspecionar inferência, testar APIs tipadas e evitar regressões no analisador.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up image of a woman's hand holding a stack of spiral-bound notebooks and papers against a dark background.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    get_type_hints no Python: leia anotações

    Aprenda get_type_hints no Python para resolver referências futuras, ler Annotated e inspecionar funções e classes com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    runtime_checkable no Python: Protocol em runtime

    Aprenda runtime_checkable no Python para testar Protocol com isinstance, entender limites e criar contratos estruturais seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026