Type Hints no Python: anotações de tipo do básico ao Protocol

Cansado de programar?

Conheça a melhor e mais completa formação de Python e Django e sinta-se um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL, HTML, CSS, Javascript, Bootstrap e muito mais!

Quero aprender Python e Django de Verdade! Quero aprender!
Suporte

Tire suas dúvidas diretamente com o professor

Projetos práticos

Projetos práticos voltados para o mercado de trabalho

Prática profissional

Formação moderna com foco na prática profissional

Resposta rápida

Type hints são anotações que dizem qual tipo uma variável, um parâmetro ou um retorno deve ter: nome: str e def dobro(x: int) -> int:. O Python não impõe esses tipos ao rodar; quem os verifica é uma ferramenta como o mypy, antes da execução.

1
2
3
4
5
def dobro(x: int) -> int:
    return x * 2

print(dobro(5))     # 10
print(dobro("ab"))  # abab (roda! só o mypy acusa o erro)

Resumo em 30 segundos:

  • Sintaxe: variavel: tipo = valor e def f(param: tipo) -> retorno:.
  • Desde o Python 3.9: list[int], dict[str, int], sem importar do typing.
  • Desde o 3.10: int | None no lugar de Optional[int].
  • Desde o 3.12: type Alias = ... para apelidos de tipo.
  • O interpretador ignora os tipos; rode mypy arquivo.py para verificá-los.

Salve salve Pythonista!

O Python é uma linguagem de tipagem dinâmica: você não declara o tipo das variáveis e isso deixa o código curto e rápido de escrever. O preço aparece quando o projeto cresce. Qual tipo essa função recebe? Esse retorno pode ser None? Quem chamou com uma string no lugar de um número?

Os type hints respondem essas perguntas no próprio código. Neste guia você vai aprender a anotar variáveis e funções, usar tipos genéricos, Optional, Union, TypedDict, Callable, apelidos de tipo e Protocol, e verificar tudo com o mypy. Os exemplos foram executados com Python 3.13 e mypy 2.3.1, e as saídas mostradas são as reais. Onde um recurso exige uma versão mínima do Python, isso está indicado.

Então… Bora pro post! :rocket:

Vá Direto ao Assunto…

O que são type hints no Python

O que são type hints? Type hints (dicas de tipo ou anotações de tipo) são marcações opcionais, definidas na PEP 484, que indicam o tipo esperado de variáveis, parâmetros e valores de retorno. Elas não mudam o comportamento do programa: servem para documentar o código, melhorar o autocompletar dos editores e permitir que verificadores estáticos, como o mypy, encontrem erros de tipo antes da execução.

Antes das anotações, a única forma de documentar tipos era em comentários ou docstrings. Hoje o editor (VS Code, PyCharm) lê as anotações e avisa na hora quando você passa o tipo errado, e bibliotecas como FastAPI e Pydantic usam as anotações para validar dados automaticamente.

A referência oficial é o módulo typing da documentação do Python, e a especificação original está na PEP 484.

Anotando variáveis e funções

Para variáveis, a sintaxe é nome: tipo = valor (PEP 526). Para funções, cada parâmetro recebe : tipo e o retorno vem depois de ->:

1
2
3
4
5
6
7
8
9
10
11
12
nome: str = "Ana"
idade: int = 30
altura: float = 1.68
ativo: bool = True


def saudacao(nome: str, vezes: int = 1) -> str:
    return f"Olá, {nome}! " * vezes


print(saudacao("Ana", 2))
print(saudacao.__annotations__)
1
2
Olá, Ana! Olá, Ana! 
{'nome': <class 'str'>, 'vezes': <class 'int'>, 'return': <class 'str'>}

Repare em três detalhes:

  • Com valor padrão, a anotação vem antes do =: vezes: int = 1.
  • As anotações ficam guardadas no atributo __annotations__, mas o Python não faz nada com elas.
  • Uma função que não retorna nada é anotada com -> None.

Se precisar revisar parâmetros, valores padrão e retorno, o post sobre funções em Python cobre tudo isso do zero.

O Python não impõe tipos em tempo de execução

Este é o ponto que mais confunde quem vem de Java ou C#: anotar um tipo não cria nenhuma verificação. O código abaixo roda do começo ao fim:

1
2
3
4
5
6
7
8
9
def dobro(x: int) -> int:
    return x * 2


print(dobro(5))
print(dobro("ab"))
print(dobro([1, 2]))
idade: int = "trinta"
print(idade, type(idade))
1
2
3
4
10
abab
[1, 2, 1, 2]
trinta <class 'str'>

Nenhum erro. O * funciona com strings e listas, então o Python executa. As anotações são informação para ferramentas, não regras para o interpretador. Por isso existe o mypy (seção adiante), que analisa o código sem executá-lo e acusa exatamente essas quatro linhas.

Quando você precisa validar dados em tempo de execução (um JSON vindo de uma API, um formulário), use uma biblioteca que lê as anotações e as aplica, como o Pydantic. Veja a nossa introdução ao Pydantic para validação de dados.

Tipos genéricos: list[int], dict[str, int] e tuplas

Dizer que algo é uma list é pouco. Lista de quê? Desde o Python 3.9 (PEP 585), os tipos embutidos aceitam colchetes para indicar o tipo dos elementos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def media(notas: list[float]) -> float:
    return sum(notas) / len(notas)


def contar_palavras(texto: str) -> dict[str, int]:
    contagem: dict[str, int] = {}
    for palavra in texto.lower().split():
        contagem[palavra] = contagem.get(palavra, 0) + 1
    return contagem


coordenada: tuple[float, float] = (-23.55, -46.63)
notas_bimestre: tuple[int, ...] = (7, 8, 10, 6)
tags: set[str] = {"python", "tipos"}

print(media([7.5, 8.0, 9.5]))
print(contar_palavras("Python é legal e Python é simples"))
1
2
8.333333333333334
{'python': 2, 'é': 2, 'legal': 1, 'e': 1, 'simples': 1}
  • list[float]: lista em que todos os itens são float.
  • dict[str, int]: chaves str, valores int.
  • tuple[float, float]: tupla de exatamente dois float. Para tamanho variável, use tuple[int, ...].
  • set[str]: conjunto de strings.

Em código antigo você vai ver from typing import List, Dict e List[int]. Funciona, mas essas formas estão obsoletas desde o 3.9: prefira as minúsculas.

Optional, X | None e Union

Muitas funções devolvem um valor ou None, como uma busca que pode não achar nada. Anotar isso corretamente é onde o type hint mais evita bugs:

1
2
3
4
5
6
7
def buscar_email(usuarios: dict[str, str], nome: str) -> str | None:
    return usuarios.get(nome)


usuarios = {"ana": "[email protected]"}
email = buscar_email(usuarios, "bruno")
print(email.upper())

Ao rodar, o programa quebra com AttributeError: 'NoneType' object has no attribute 'upper'. O mypy avisa antes de rodar:

1
2
opcional.py:7: error: Item "None" of "str | None" has no attribute "upper"  [union-attr]
Found 1 error in 1 file (checked 1 source file)

A correção é tratar o None explicitamente. Depois do if email is not None, o mypy sabe que email é str (isso se chama type narrowing, ou estreitamento de tipo):

1
2
3
4
5
6
7
8
9
10
11
12
13
from typing import Optional


def buscar_email(usuarios: dict[str, str], nome: str) -> Optional[str]:
    return usuarios.get(nome)


usuarios = {"ana": "[email protected]"}
email = buscar_email(usuarios, "bruno")
if email is not None:
    print(email.upper())
else:
    print("Usuário não encontrado")
1
Usuário não encontrado

Optional[str] e str | None são o mesmo tipo. A forma com | existe desde o Python 3.10 (PEP 604) e é a recomendada. Apesar do nome, Optional não significa “parâmetro opcional”; significa “pode ser None”.

Para “um tipo ou outro”, use | (ou o antigo Union):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from typing import Union


def formatar_id(valor: int | str) -> str:
    if isinstance(valor, int):
        return f"ID-{valor:05d}"
    return f"ID-{valor.upper()}"


def formatar_id_antigo(valor: Union[int, str]) -> str:
    return str(valor)


print(formatar_id(42))
print(formatar_id("abc"))
1
2
ID-00042
ID-ABC

O isinstance() também estreita o tipo: dentro do if, valor é int; depois dele, só pode ser str, e o mypy aceita o .upper().

Está curtindo esse conteúdo? :thumbsup:

Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?

Sua assinatura não pôde ser validada.
Você fez sua assinatura com sucesso.

Assine as PyDicas e receba 30 dias do melhor conteúdo Python na sua Caixa de Entrada: direto e sem enrolação!

TypedDict, Callable, apelidos de tipo e Protocol

TypedDict: dicionários com chaves conhecidas

dict[str, int] serve quando todos os valores têm o mesmo tipo. Para um dicionário com chaves fixas e tipos diferentes por chave (como um JSON de aluno), use TypedDict:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from typing import TypedDict


class Aluno(TypedDict):
    nome: str
    matricula: int
    notas: list[float]


def resumo(aluno: Aluno) -> str:
    media = sum(aluno["notas"]) / len(aluno["notas"])
    return f"{aluno['nome']} ({aluno['matricula']}): média {media:.1f}"


ana: Aluno = {"nome": "Ana", "matricula": 1234, "notas": [8.0, 9.5]}
print(resumo(ana))
print(type(ana))

bruno: Aluno = {"nome": "Bruno", "matricula": "5678", "notas": [7.0]}
carla: Aluno = {"nome": "Carla", "notas": [10.0]}
1
2
Ana (1234): média 8.8
<class 'dict'>

Em tempo de execução, ana é um dict comum, e bruno e carla são criados sem erro. Já o mypy aponta os dois problemas:

1
2
3
typeddict.py:19: error: Incompatible types (expression has type "str", TypedDict item "matricula" has type "int")  [typeddict-item]
typeddict.py:20: error: Missing key "matricula" for TypedDict "Aluno"  [typeddict-item]
Found 2 errors in 1 file (checked 1 source file)

Se você quer um objeto com atributos (aluno.nome em vez de aluno["nome"]), uma dataclass costuma ser a escolha melhor. TypedDict brilha quando os dados já são dicionários, como respostas de APIs.

Callable: funções como parâmetro

Para anotar um parâmetro que recebe uma função, use Callable[[tipos dos argumentos], tipo do retorno], importado de collections.abc:

1
2
3
4
5
6
7
8
9
10
11
12
13
from collections.abc import Callable


def aplicar(funcao: Callable[[int], int], valores: list[int]) -> list[int]:
    return [funcao(v) for v in valores]


def quadrado(x: int) -> int:
    return x * x


print(aplicar(quadrado, [1, 2, 3]))
print(aplicar(lambda x: x + 10, [1, 2, 3]))
1
2
[1, 4, 9]
[11, 12, 13]

Se alguém passar uma função com a assinatura errada, por exemplo def saudar(nome: str) -> str, o mypy acusa:

1
callable_err.py:12: error: Argument 1 to "aplicar" has incompatible type "Callable[[str], str]"; expected "Callable[[int], int]"  [arg-type]

Apelidos de tipo: TypeAlias e type (Python 3.12+)

Quando um tipo fica longo ou se repete, dê um nome a ele. A forma compatível com o Python 3.10 e o 3.11 usa TypeAlias:

1
2
3
4
5
6
7
8
9
10
11
12
from typing import TypeAlias

Coordenada: TypeAlias = tuple[float, float]
Rota: TypeAlias = list[Coordenada]


def trechos(rota: Rota) -> int:
    return len(rota) - 1


trajeto: Rota = [(-23.55, -46.63), (-22.90, -43.17), (-15.79, -47.88)]
print(trechos(trajeto))
1
2

A partir do Python 3.12 (PEP 695) existe o comando type, mais limpo:

1
2
type Coordenada = tuple[float, float]
type Rota = list[Coordenada]

Atenção à versão: no Python 3.11 ou anterior, essa linha nem compila e gera SyntaxError: invalid syntax. Se o seu projeto precisa rodar em versões mais antigas, fique com TypeAlias.

Protocol: tipagem estrutural (introdução)

Às vezes você não se importa com a classe do objeto, só com o que ele sabe fazer. É o famoso duck typing: “se anda como pato e grasna como pato, é um pato”. O Protocol leva essa ideia para os type hints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from typing import Protocol


class TemArea(Protocol):
    def area(self) -> float: ...


class Quadrado:
    def __init__(self, lado: float) -> None:
        self.lado = lado

    def area(self) -> float:
        return self.lado ** 2


class Circulo:
    def __init__(self, raio: float) -> None:
        self.raio = raio

    def area(self) -> float:
        return 3.14159 * self.raio ** 2


class Texto:
    def __init__(self, conteudo: str) -> None:
        self.conteudo = conteudo


def area_total(formas: list[TemArea]) -> float:
    return sum(forma.area() for forma in formas)


print(area_total([Quadrado(2), Circulo(1)]))
area_total([Quadrado(2), Texto("oi")])

Quadrado e Circulo não herdam de TemArea, mas têm o método area() com a assinatura certa, e isso basta. Texto não tem, e o mypy percebe antes que o programa quebre com AttributeError:

1
2
protocolo.py:34: error: List item 1 has incompatible type "Texto"; expected "TemArea"  [list-item]
Found 1 error in 1 file (checked 1 source file)

A primeira chamada imprime 7.14159. Protocol é um recurso mais avançado; para começar, basta saber que ele existe e resolve o caso “aceito qualquer objeto que tenha tal método”.

Tipos ajudam a documentar a intenção do código, mas quem sustenta tudo isso são funções e classes bem escritas - essa é a base que você constrói na prática, do zero ao avançado, na Jornada Python:

Verificando tipos com mypy

O mypy é o verificador de tipos mais tradicional do ecossistema (o Pyright, usado pelo VS Code, é outra opção popular). Instale no seu ambiente virtual:

1
2
pip install mypy
mypy --version
1
mypy 2.3.1 (compiled: yes)

Considere este loja.py, que tem dois bugs escondidos:

1
2
3
4
5
6
7
8
9
10
11
def calcular_total(precos: list[float], desconto: float = 0.0) -> float:
    return sum(precos) * (1 - desconto)


def formatar(valor: float) -> str:
    return f"R$ {valor:.2f}"


total = calcular_total([10.0, 20.0], "10%")
mensagem: str = calcular_total([5.0])
print(formatar(total))

Rode mypy loja.py:

1
2
3
loja.py:9: error: Argument 2 to "calcular_total" has incompatible type "str"; expected "float"  [arg-type]
loja.py:10: error: Incompatible types in assignment (expression has type "float", variable has type "str")  [assignment]
Found 2 errors in 1 file (checked 1 source file)

Cada linha traz arquivo:linha, a descrição e, entre colchetes, o código do erro (útil para pesquisar na documentação). O primeiro bug derrubaria o programa com TypeError: unsupported operand type(s) for -: 'int' and 'str'; o segundo passaria despercebido e guardaria um número onde o resto do código espera texto. Corrigindo para calcular_total([10.0, 20.0], 0.10) e formatar(calcular_total([5.0])), o mypy responde:

1
Success: no issues found in 1 source file

Dicas para usar o mypy no dia a dia:

  • Rode em uma pasta inteira com mypy meu_pacote/.
  • Por padrão, o mypy não verifica o corpo de funções sem anotação (veja em Erros comuns). Isso permite adotar tipos aos poucos, em código existente.
  • mypy --strict liga todas as verificações rígidas, incluindo exigir anotação em toda função. Ótimo para projetos novos.
  • Configure opções fixas no pyproject.toml, na seção [tool.mypy].

A documentação completa está em mypy.readthedocs.io.

Qual tipo usar em cada situação

Situação Anotação Versão mínima
Valor simples int, str, float, bool 3.6+
Lista de itens do mesmo tipo list[int] 3.9+
Dicionário com chaves e valores uniformes dict[str, int] 3.9+
Tupla de tamanho fixo tuple[float, float] 3.9+
Pode ser None int | None (ou Optional[int]) 3.10+ (Optional: 3.5+)
Um tipo ou outro int | str (ou Union[int, str]) 3.10+ (Union: 3.5+)
Dicionário com chaves fixas TypedDict 3.8+
Função como parâmetro Callable[[int], int] 3.9+ (de collections.abc)
Apelido para um tipo longo type Nome = ... (ou TypeAlias) 3.12+ (TypeAlias: 3.10+)
“Qualquer objeto com o método X” Protocol 3.8+
Função que não retorna nada -> None 3.5+

Na dúvida entre TypedDict, dataclass e Pydantic: TypedDict descreve dicionários que você já recebe, dataclass cria objetos com atributos no seu código, e Pydantic valida dados externos em tempo de execução.

Erros comuns

Estes são os erros que mais aparecem quando se começa a usar type hints, com a mensagem real do Python 3.13 ou do mypy 2.3.1.

Achar que a anotação valida o valor em tempo de execução

1
2
3
4
5
def soma(a: int, b: int) -> int:
    return a + b


print(soma("1", "2"))
1
12

Não há erro: o Python concatena as strings e devolve '12'. Correção: rode o mypy (Argument 1 to "soma" has incompatible type "str"; expected "int") ou, se os dados vêm de fora (usuário, API), valide com isinstance() ou Pydantic.

Usar um valor que pode ser None sem checar

1
2
3
4
5
6
7
8
def primeiro_par(numeros: list[int]) -> int | None:
    for n in numeros:
        if n % 2 == 0:
            return n
    return None


print(primeiro_par([3, 5, 7]) * 10)
1
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'

O mypy avisa antes: Unsupported operand types for * ("None" and "int") [operator]. Correção: guarde o resultado em uma variável e teste if resultado is not None: antes de usar.

Usar um tipo genérico no isinstance()

1
2
valores = [1, 2, 3]
print(isinstance(valores, list[int]))
1
TypeError: isinstance() argument 2 cannot be a parameterized generic

Correção: isinstance() só conhece a classe, não o tipo dos elementos. Use isinstance(valores, list) e, se precisar, confira os itens com all(isinstance(v, int) for v in valores).

Usar o comando type em Python anterior ao 3.12

1
type Coordenada = tuple[float, float]
1
SyntaxError: invalid syntax

Correção: atualize para o Python 3.12+ ou use Coordenada: TypeAlias = tuple[float, float] (com from typing import TypeAlias), que funciona desde o 3.10.

Esquecer de anotar a função e achar que o mypy verificou

1
2
3
def total(precos):
    soma: int = "zero"
    return sum(precos)
1
2
so_total.py:2: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
Success: no issues found in 1 source file

A atribuição errada passa com Success, só com uma nota. Como total() não tem nenhuma anotação, o mypy pula o corpo dela. Correção: anote os parâmetros e o retorno (def total(precos: list[float]) -> float:) ou rode com --check-untyped-defs ou --strict.

Exercícios resolvidos

Tente resolver cada exercício antes de abrir a solução. Todas as soluções foram executadas no Python 3.13 e passaram no mypy 2.3.1 sem erros (exceto onde o erro é o objetivo). Para praticar mais a lógica, visite a nossa página de exercícios de Python resolvidos.

Exercício 1. Adicione type hints à função abaixo, que junta uma lista de palavras com um separador:

1
2
def juntar(palavras, separador=" "):
    return separador.join(palavras)
Ver solução
1
2
3
4
5
def juntar(palavras: list[str], separador: str = " ") -> str:
    return separador.join(palavras)


print(juntar(["type", "hints"], "-"))

Saída: type-hints

O valor padrão continua depois do tipo (separador: str = " "), e o retorno de str.join() é sempre str.

Exercício 2. Escreva agrupar_por_inicial(nomes), anotada, que recebe uma lista de nomes e devolve um dicionário da inicial (maiúscula) para a lista de nomes com aquela inicial.

Ver solução
1
2
3
4
5
6
7
8
9
def agrupar_por_inicial(nomes: list[str]) -> dict[str, list[str]]:
    grupos: dict[str, list[str]] = {}
    for nome in nomes:
        inicial = nome[0].upper()
        grupos.setdefault(inicial, []).append(nome)
    return grupos


print(agrupar_por_inicial(["Ana", "Bruno", "amanda", "Beatriz", "Caio"]))

Saída: {'A': ['Ana', 'amanda'], 'B': ['Bruno', 'Beatriz'], 'C': ['Caio']}

Tipos genéricos podem ser aninhados: dict[str, list[str]]. A anotação na variável grupos ajuda o mypy, que não consegue adivinhar o tipo de um {} vazio.

Exercício 3. Escreva primeiro_par(numeros) que devolve o primeiro número par da lista ou None se não houver. Anote corretamente e use o resultado de forma que o mypy aceite.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
def primeiro_par(numeros: list[int]) -> int | None:
    for n in numeros:
        if n % 2 == 0:
            return n
    return None


resultado = primeiro_par([3, 5, 7])
if resultado is None:
    print("Nenhum número par")
else:
    print(resultado * 10)

Saída: Nenhum número par

Sem o if resultado is None, o mypy reclama de resultado * 10, porque None * 10 quebraria. Dentro do else, o tipo foi estreitado para int.

Exercício 4. Crie um TypedDict chamado Produto com nome (str), preco (float) e estoque (int), e uma função que calcula o valor total em estoque de uma lista de produtos.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from typing import TypedDict


class Produto(TypedDict):
    nome: str
    preco: float
    estoque: int


def valor_em_estoque(produtos: list[Produto]) -> float:
    return sum(p["preco"] * p["estoque"] for p in produtos)


itens: list[Produto] = [
    {"nome": "Caderno", "preco": 25.0, "estoque": 10},
    {"nome": "Caneta", "preco": 3.5, "estoque": 100},
]
print(valor_em_estoque(itens))

Saída: 600.0

Os itens continuam sendo dicionários comuns, mas agora o mypy confere se cada um tem as três chaves com os tipos certos.

Exercício 5. Escreva aplicar_n_vezes(funcao, texto, vezes), que aplica uma função de str para str sobre o texto repetidas vezes. Anote o parâmetro funcao com Callable.

Ver solução
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections.abc import Callable


def aplicar_n_vezes(funcao: Callable[[str], str], texto: str, vezes: int) -> str:
    for _ in range(vezes):
        texto = funcao(texto)
    return texto


def exclamar(s: str) -> str:
    return s + "!"


print(aplicar_n_vezes(exclamar, "Python", 3))

Saída: Python!!!

Callable[[str], str] significa “uma função que recebe um str e devolve um str”. Passar uma função int -> int geraria erro [arg-type] no mypy.

Exercício 6. Usando o comando type do Python 3.12+, crie o apelido Matriz para uma lista de listas de inteiros e escreva transpor(m), que troca linhas por colunas.

Ver solução
1
2
3
4
5
6
7
8
type Matriz = list[list[int]]


def transpor(m: Matriz) -> Matriz:
    return [list(linha) for linha in zip(*m)]


print(transpor([[1, 2, 3], [4, 5, 6]]))

Saída: [[1, 4], [2, 5], [3, 6]]

O apelido deixa a assinatura legível. Em Python 3.10 ou 3.11, troque a primeira linha por Matriz: TypeAlias = list[list[int]].

Exercício 7 (estilo prova). O que acontece ao executar o código abaixo com python?

1
2
3
4
5
def soma(a: int, b: int) -> int:
    return a + b


print(soma("1", "2"))

a) TypeError, porque os argumentos não são int
b) Imprime 3
c) Imprime 12
d) SyntaxError, porque a anotação está errada

Ver solução

Saída: 12

Resposta: c. O Python não impõe as anotações em tempo de execução. Como "1" + "2" é uma concatenação válida, o resultado é a string '12'. Só o mypy acusaria o erro, com duas mensagens [arg-type], uma para cada argumento.

Exercício 8 (estilo prova). Em Python 3.10+, qual anotação representa “uma lista de strings ou None” (ou seja, o parâmetro pode receber None no lugar da lista)?

a) list[str] | None
b) list[str | None]
c) list[None]
d) list(str) or None

Ver solução

Resposta: a. Em (a), o None está fora dos colchetes: o valor inteiro pode ser uma lista de str ou None. Em (b), o None está dentro: é sempre uma lista, cujos itens podem ser str ou None. O mypy confirma: passar None para (b) gera Argument 1 to "c" has incompatible type "None"; expected "list[str | None]". (c) aceita só listas de None e (d) não é uma anotação de tipo válida.

Quer firmar funções, POO e boas práticas de Python em projetos guiados? Conheça o nosso curso de Python completo.

Conclusão

Neste guia de type hints no Python, você aprendeu:

✅ Anotações básicas - nome: str e def f(x: int) -> int:
✅ Tipos genéricos - list[int], dict[str, int], tuple[float, float]
✅ Optional e Union - valores que podem ser None ou de mais de um tipo
✅ TypedDict, Callable, type e Protocol - dicionários, funções, apelidos e duck typing
✅ mypy - como rodar e ler a saída

Principais lições:

  • O Python não verifica tipos ao rodar; o mypy verifica antes
  • int | None (3.10+) é o mesmo que Optional[int]
  • type Alias = ... exige Python 3.12+
  • Funções sem anotação não são verificadas pelo mypy por padrão

Próximos passos:

  • Anote as funções de um projeto seu e rode mypy nele
  • Conheça as dataclasses, que usam type hints para gerar classes
  • Valide dados de verdade com o Pydantic

Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! :wink:

Perguntas frequentes

O que são type hints no Python?

Type hints (dicas de tipo) são anotações que indicam o tipo esperado de variáveis, parâmetros e retornos, como def dobro(x: int) -> int:. Elas foram introduzidas pela PEP 484 e servem para documentar o código, melhorar o autocompletar do editor e permitir que ferramentas como o mypy encontrem erros antes de o programa rodar.

O Python obriga o uso dos tipos anotados?

Não. O interpretador ignora as anotações em tempo de execução: dobro('ab') roda e devolve 'abab' mesmo com x: int. Quem verifica os tipos é uma ferramenta externa, como o mypy ou o Pyright, executada antes do programa. Para validar dados em tempo de execução, use bibliotecas como o Pydantic.

Qual a diferença entre Optional[str] e str ou None com o operador barra vertical?

Nenhuma no significado: Optional[str], Union[str, None] e a forma nova com o operador barra vertical entre str e None representam o mesmo tipo, um texto ou None. A forma com o operador existe desde o Python 3.10 (PEP 604) e é a recomendada em código novo. Optional vem do módulo typing e continua válido.

Preciso importar List e Dict do typing?

Não, a partir do Python 3.9 você pode usar os tipos embutidos diretamente com colchetes: list[int], dict[str, int], tuple[float, float] e set[str]. typing.List e typing.Dict continuam funcionando, mas estão obsoletos desde o Python 3.9 e só são necessários em código que precisa rodar no 3.8 ou anterior.

Como verificar os tipos com mypy?

Instale com pip install mypy dentro do ambiente virtual e rode mypy arquivo.py ou mypy pasta/. O mypy lista cada problema com arquivo, linha, mensagem e código do erro entre colchetes, como [arg-type]. Se não houver problemas, ele mostra Success: no issues found. A opção --strict ativa verificações mais rígidas.

Type hints deixam o Python mais rápido?

Não. O CPython não usa as anotações para otimizar a execução, então o desempenho é o mesmo com ou sem type hints. O ganho está em encontrar erros mais cedo, facilitar a leitura, melhorar o autocompletar e tornar refatorações mais seguras. Algumas ferramentas, como o mypyc, compilam código anotado, mas isso é um passo extra e opcional.

O que é o comando type do Python 3.12?

É a sintaxe nova para criar apelidos de tipo (PEP 695): type Coordenada = tuple[float, float]. Ela só existe a partir do Python 3.12; em versões anteriores gera SyntaxError. Para compatibilidade com o 3.10 e o 3.11, use Coordenada: TypeAlias = tuple[float, float], importando TypeAlias do módulo typing.

O que acontece ao executar def soma(a: int, b: int) -> int: return a + b com soma(‘1’, ‘2’)?

O código roda sem erro e devolve '12', porque o Python não impõe as anotações em tempo de execução e o operador + concatena strings. Só uma ferramenta de verificação estática, como o mypy, aponta o problema: Argument 1 to "soma" has incompatible type "str"; expected "int".

Começe agora sua Jornada na Programação!

Não deixe para amanhã o sucesso que você pode começar a construir hoje!

#newsletter Olá :wave: Curtiu o artigo? Então faça parte da nossa Newsletter! Privacidade Não se preocupe, respeitamos sua privacidade. Você pode se descadastrar a qualquer momento.