Python Dicas de Tipo (Type Hints)

Olhe esta linha: def total(preco, quantidade):. O preço é um número com casas decimais ou um texto como "R$ 2,50"? A quantidade pode ser 1.5? E a função devolve o quê? No Python, nada disso precisa ser dito: qualquer variável aceita qualquer tipo. Mas você pode dizer, escrevendo dicas de tipo (em inglês, type hints): def total(preco: float, quantidade: int) -> float:. Elas deixam o código mais fácil de entender, ajudam o editor de código a sugerir e avisar, e ferramentas próprias conseguem achar erros antes de você rodar o programa. Só tem um detalhe importante, e é o centro desta aula: o Python, sozinho, não confere essas dicas.

Você vai aprender
  • A anotar o tipo dos parâmetros (n: int), do valor devolvido (-> int) e das variáveis.
  • Que as dicas são só dicas: o programa roda mesmo com o tipo errado (e você vai ver o que acontece).
  • A escrever tipos de coleções: list[float], dict[str, float], tuple[str, int].
  • A dizer "pode ser este tipo ou nada" com | None.
  • Como uma ferramenta como o mypy usa as dicas para achar erros.
  • As anotações "preguiçosas" do Python 3.14 3.14 e o __annotations__.

A ideia: a etiqueta da tomada

Ao lado de algumas tomadas da casa da Ana há uma etiqueta: 220V. A etiqueta não impede ninguém de ligar ali um secador de 127V; ela só avisa. Quem lê a etiqueta antes não queima o secador.

As dicas de tipo são essas etiquetas. quantidade: int avisa "aqui vai um número inteiro". O Python não impede você de passar um texto, do mesmo jeito que a etiqueta não trava a tomada. Mas quem lê o código, o editor de código e as ferramentas de checagem leem a etiqueta e avisam antes do estrago.


1. Anotando uma função

def dobro(n: int) -> int:
    return n * 2


print(dobro(5))
n: int
Depois do nome do parâmetro, dois-pontos e o tipo esperado: "n deve ser um int" (número inteiro; aula Tipos de Dados).
-> int
Uma setinha (hífen e sinal de maior) antes dos dois-pontos do def: o tipo do valor que a função devolve com o return (aula return). Lê-se "devolve um int".
:
O dois-pontos do fim do def continua lá, depois da setinha.
O resto
O corpo da função é igual ao de sempre. As dicas não mudam o que a função faz.

Com vários parâmetros, cada um ganha a sua dica:

def total(preco: float, quantidade: int) -> float:
    return preco * quantidade

Uma função que não devolve nada (só mostra algo na tela, por exemplo) leva -> None (aula O Valor None):

def avisar(mensagem: str) -> None:
    print("Atenção:", mensagem)

Os tipos mais comuns

DicaQuer dizerExemplo de valor
intNúmero inteiro3
floatNúmero com casas decimais (aceita inteiros também)2.5
strTexto"Ana"
boolTrue ou FalseTrue
NoneNada (no retorno: "não devolve nada")None

2. O Python não confere as dicas

Agora o teste. A função diz que espera um int. O que acontece se você passar um texto, ou uma lista?

def dobro(n: int) -> int:
    return n * 2


print(dobro(5))
print(dobro("ab"))
print(dobro([1, 2]))
$ python3 dobro.py
10
abab
[1, 2, 1, 2]

Rodou, sem nenhuma reclamação. O Python ignorou a dica e fez o que * 2 faz com cada tipo: com número, multiplica; com texto, repete ("ab" * 2 é "abab", aula Juntar e Repetir Textos); com lista, também repete. A função "funcionou", mas devolveu um texto e uma lista, quando a etiqueta prometia um int.

Às vezes, o tipo errado não passa calado: ele explode lá dentro da função, longe de onde o erro de verdade aconteceu.

def total(preco: float, quantidade: int) -> float:
    return preco * quantidade


print(total(2.5, 3))
print(total(2.5, "3"))
$ python3 total.py
7.5
Traceback (most recent call last):
  File "/home/ana/total.py", line 6, in <module>
    print(total(2.5, "3"))
          ~~~~~^^^^^^^^^^
  File "/home/ana/total.py", line 2, in total
    return preco * quantidade
           ~~~~~~^~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'float'
7.5
A primeira chamada, com os tipos certos, deu certo: 2.5 × 3.
line 6, in <module>
A chamada com o problema: total(2.5, "3"). O "3" é um texto, com aspas.
line 2, in total
O erro estourou dentro da função, na conta. O ^ embaixo do * mostra a operação que falhou.
TypeError: can't multiply sequence by non-int of type 'float'
"Não dá para multiplicar uma sequência (o texto "3") por um não inteiro do tipo float (o 2.5)." Texto vezes inteiro repete o texto; texto vezes 2.5 não faz sentido.

Repare: a dica quantidade: int estava lá o tempo todo, e o Python não a usou para avisar "você passou um str onde devia ir um int". Ele só descobriu o problema quando a conta falhou. Por isso as dicas, sozinhas, são documentação. Para que elas encontrem erros, é preciso uma ferramenta que as leia (seção 5).

Missão: a quantidade chegou do input(), como texto, e o programa dá TypeError. A dica quantidade: int diz o que a função espera: converta a quantidade com int() antes de chamar a função (aula Conversão de Tipos).

def total(preco: float, quantidade: int) -> float:
    return preco * quantidade


quantidade = "3"  # veio do input(), é texto
print(total(2.5, quantidade))
def total(preco: float, quantidade: int) -> float:
    return preco * quantidade


quantidade = "3"  # veio do input(), é texto
print(total(2.5, int(quantidade)))

3. Dicas em variáveis e em coleções

Variáveis também podem ter dica: o nome, dois-pontos, o tipo, e depois o = de sempre. E para listas e dicionários, você diz o tipo dos itens entre colchetes:

def media(notas: list[float]) -> float:
    return sum(notas) / len(notas)


turma: list[float] = [7.5, 8.0, 6.5]
resultado: float = media(turma)
print(resultado)
print(media.__annotations__)
$ python3 media_tipos.py
7.333333333333333
{'notas': list[float], 'return': <class 'float'>}
list[float]
"Uma lista de float": a lista (aula Listas) e, entre colchetes, o tipo de cada item.
turma: list[float] = […]
Uma variável com dica. O = [7.5, 8.0, 6.5] é a atribuição comum.
media.__annotations__
As dicas ficam guardadas na função, num dicionário chamado __annotations__ ("anotações", o nome técnico das dicas). A chave 'return' guarda a dica do ->. É assim que o editor de código e as ferramentas leem as dicas.
<class 'float'>
É como o Python mostra o próprio tipo float (aula Descobrindo o Tipo).
DicaQuer dizerExemplo de valor
list[int]Lista de inteiros[3, 1, 2]
list[str]Lista de textos["arroz", "sal"]
dict[str, float]Dicionário: chaves texto, valores float{"pão": 0.75}
tuple[str, int]Tupla de dois itens: um texto e um inteiro("Ana", 30)
set[str]Conjunto de textos{"PI", "CE"}
Só a dica, sem valor

Uma dica sozinha não cria a variável:

quantidade: int
print(quantidade)
$ python3 so_anotacao.py
Traceback (most recent call last):
  File "/home/ana/so_anotacao.py", line 2, in <module>
    print(quantidade)
          ^^^^^^^^^^
NameError: name 'quantidade' is not defined

"O nome quantidade não está definido." A linha 1 só disse que tipo a variável vai ter; ninguém guardou um valor nela. Como consertar: quantidade: int = 0 (ou o valor que fizer sentido).

Missão: acrescente as dicas de tipo à função: preco e desconto são float, e ela devolve um float. A saída continua 45.0.

def preco_final(preco, desconto):
    return preco - preco * desconto


print(preco_final(50.0, 0.1))
def preco_final(preco: float, desconto: float) -> float:
    return preco - preco * desconto


print(preco_final(50.0, 0.1))

4. "Pode não ter": | None

A Ana tem uma tabela de preços e uma função que procura um produto nela. Se o produto existe, a função devolve o preço; se não existe, devolve None. Como dizer isso na dica? Com a barra vertical |, que aqui quer dizer "ou":

def buscar_preco(produto: str, tabela: dict[str, float]) -> float | None:
    if produto in tabela:
        return tabela[produto]
    return None


precos = {"pão": 0.75, "leite": 5.5}
print(buscar_preco("leite", precos))
print(buscar_preco("café", precos))
$ python3 busca.py
5.5
None
-> float | None
"Devolve um float ou None." Quem chama a função fica avisado: o resultado pode não ser um número, e é preciso tratar esse caso (com um if preco is None:, aula is e in).
dict[str, float]
A tabela: nomes de produtos (texto) ligados a preços (float).

O uso mais comum do | None é num parâmetro com valor padrão None (aula Valores Padrão):

def saudacao(nome: str | None = None) -> str:
    if nome is None:
        return "Olá, visitante!"
    return f"Olá, {nome}!"


print(saudacao("Ana"))
print(saudacao())
$ python3 saudacao.py
Olá, Ana!
Olá, visitante!

A ordem é: o nome, a dica (str | None) e depois o valor padrão (= None). O mesmo | serve para outros tipos: int | float quer dizer "inteiro ou com casas decimais".

O Optional dos códigos mais antigos

Em código escrito para versões antigas do Python, você vai encontrar Optional[str] (do módulo typing) no lugar de str | None, e List[int], com L maiúsculo, no lugar de list[int]. Querem dizer a mesma coisa. Nas versões atuais (o 3.14 do guia, o 3.13 do Debian), prefira as formas curtas, str | None e list[int], que não precisam de import.


5. Quem confere as dicas: o mypy

Se o Python não confere, para que escrever? Porque outros programas conferem. O mais conhecido é o mypy: ele lê o seu arquivo, sem rodá-lo, e compara cada chamada com as dicas. O mypy não vem com o Python; ele é instalado com o pip, dentro de um ambiente virtual (as aulas pip e venv mostram como). Os editores de código, como o VS Code, fazem uma checagem parecida enquanto você digita e sublinham os erros em vermelho.

A Ana juntou os erros desta aula num arquivo:

def dobro(n: int) -> int:
    return n * 2


def total(preco: float, quantidade: int) -> float:
    return preco * quantidade


print(dobro("ab"))
print(total(2.5, "3"))
idade: int = 30
idade = "trinta"

Ela instalou o mypy num ambiente virtual (.venv) e mandou conferir. A saída abaixo é real, com a versão do mypy do dia (2.4.0); em outra versão, os detalhes podem mudar:

$ .venv/bin/mypy checar.py
checar.py:9: error: Argument 1 to "dobro" has incompatible type "str"; expected "int"  [arg-type]
checar.py:10: error: Argument 2 to "total" has incompatible type "str"; expected "int"  [arg-type]
checar.py:12: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
Found 3 errors in 1 file (checked 1 source file)
$ .venv/bin/mypy saudacao.py busca.py
Success: no issues found in 2 source files
checar.py:9:
O arquivo e a linha do problema.
Argument 1 to "dobro" has incompatible type "str"; expected "int"
"O argumento 1 de dobro tem o tipo incompatível str; esperava int." Exatamente o aviso que o Python, sozinho, não deu: o dobro("ab") rodou calado.
checar.py:10: … Argument 2 to "total"
O "3" do total, encontrado antes de rodar, sem esperar a conta explodir lá dentro.
checar.py:12: Incompatible types in assignment
"Tipos incompatíveis na atribuição": a variável idade foi anunciada como int e recebeu um texto. O Python aceitaria sem reclamar.
Success: no issues found
"Sucesso: nenhum problema encontrado" nos arquivos saudacao.py e busca.py, que usam as dicas direito.
Vale a pena para quem está começando?

Em programas pequenos, como os das aulas, as dicas são opcionais. Elas começam a valer muito quando o programa cresce e é dividido em vários arquivos, ou quando outras pessoas vão usar as suas funções: a dica responde na hora "o que eu passo aqui?", sem ninguém precisar ler o corpo da função. Junto com a docstring, é a melhor etiqueta que uma função pode ter.


6. As anotações preguiçosas do Python 3.14 3.14

No Python 3.14, as dicas passaram a ser avaliadas de um jeito "preguiçoso" (é a mudança descrita nas PEPs 649 e 749). Até o 3.13, o Python calculava as dicas no momento em que lia o def. Agora, ele guarda as dicas sem calculá-las, e só as calcula quando alguém pede, por exemplo ao ler o __annotations__.

Na prática, isso deixa você usar numa dica um nome que só vai existir mais adiante no arquivo. Aqui, a função usa o tipo Produto, que é uma classe (aula Classes e Objetos) criada só depois:

def cadastrar(produto: Produto) -> None:
    print("Cadastrado:", produto.nome)


class Produto:
    def __init__(self, nome: str):
        self.nome = nome


cadastrar(Produto("café"))
print(cadastrar.__annotations__)
$ python3 adiantado.py
Cadastrado: café
{'produto': <class '__main__.Produto'>, 'return': None}

Quando o Python leu o def cadastrar, o nome Produto ainda não existia, e não houve erro. Na última linha, quando o __annotations__ foi pedido, a classe já existia, e a dica foi calculada sem problema.

Se você tem o Python 3.13 (o do Debian 13) ou mais antigo

Nessas versões, as dicas são calculadas na hora do def. Pela documentação oficial (What's New in Python 3.14), o adiantado.py acima pararia logo na linha 1 com um NameError, porque Produto ainda não existe. (Não rodamos isso: o laboratório do guia tem o 3.14.) O jeito antigo de contornar é escrever a dica entre aspas, produto: "Produto", o que funciona em todas as versões. No 3.14, as aspas deixaram de ser necessárias.

Um nome que não existe, em nenhum momento

A preguiça tem um outro lado: um erro de digitação numa dica também só aparece quando alguém pede as dicas.

def dobro(n: inteiro) -> inteiro:
    return n * 2


print(dobro(5))
print(dobro.__annotations__)
$ python3 inexistente.py
10
Traceback (most recent call last):
  File "/home/ana/inexistente.py", line 6, in <module>
    print(dobro.__annotations__)
          ^^^^^^^^^^^^^^^^^^^^^
  File "/home/ana/inexistente.py", line 1, in __annotate__
    def dobro(n: inteiro) -> inteiro:
                 ^^^^^^^
NameError: name 'inteiro' is not defined
10
A função funcionou: as dicas não são calculadas para rodar a função.
line 6, in <module>
O erro só veio quando o programa pediu dobro.__annotations__.
line 1, in __annotate__
__annotate__ é a funçãozinha escondida em que o Python 3.14 guarda as dicas para calcular depois. Ela rodou agora, e o ^^^^^^^ aponta para inteiro.
NameError: name 'inteiro' is not defined
"O nome inteiro não está definido." O tipo dos inteiros, em Python, se chama int.

Como consertar: def dobro(n: int) -> int:. O mypy e os editores de código apontam esse erro na hora, sem precisar rodar.


Formas diferentes de fazer a mesma coisa

def total(preco, quantidade):
    return preco * quantidade
def total(preco, quantidade):
    """Devolve o preço (float, em reais) vezes a quantidade (int)."""
    return preco * quantidade
def total(preco: float, quantidade: int) -> float:
    """Devolve o valor total da compra, em reais."""
    return preco * quantidade
FormaQuando usar
Sem dicasProgramas pequenos e rápidos, como os exercícios das aulas. É Python válido e muito comum
Os tipos só na docstringFunciona para quem lê, mas o editor de código e o mypy não entendem texto livre
Dicas de tipo + docstringFunções que outras pessoas (ou você, daqui a meses) vão usar, e programas maiores. As dicas dizem os tipos; a docstring diz o resto

Quando dá errado

A seta errada: => no lugar de ->

Quem conhece JavaScript costuma escrever a seta com um sinal de igual:

def dobro(n: int) => int:
    return n * 2
$ python3 seta.py
  File "/home/ana/seta.py", line 1
    def dobro(n: int) => int:
                      ^
SyntaxError: expected ':'

"Esperava :." Depois dos parênteses do def, o Python só aceita a seta -> (hífen e maior) ou os dois-pontos. O ^ aponta para o =. Não há Traceback: o programa nem começou a rodar. Como consertar: def dobro(n: int) -> int:.

Achar que a dica converte o valor

A dica quantidade: int não transforma o "3" em 3. Você viu na seção 2: o texto chegou como texto até a conta, e lá veio o TypeError: can't multiply sequence by non-int of type 'float'. Com dobro("ab") foi pior: nenhum erro, e um resultado errado (abab). Como consertar: converta você mesmo, com int() e float(), o que vem do input() ou de arquivos; e use o mypy ou o editor de código para conferir as dicas.


Referência rápida

EscritaO que diz
def f(n: int) -> int:Recebe um inteiro, devolve um inteiro
def f(msg: str) -> None:Recebe um texto, não devolve nada
idade: int = 30Variável com dica e valor
list[float], set[str]Coleção e o tipo dos itens
dict[str, float]Dicionário: tipo das chaves, tipo dos valores
tuple[str, int]Tupla com um tipo para cada posição
float | NoneUm float ou None
nome: str | None = NoneParâmetro opcional
f.__annotations__O dicionário com as dicas da função
mypy arquivo.pyConfere as dicas sem rodar (instale com pip numa venv)
SintomaCausa provável
O programa roda com o tipo errado, sem avisoNormal: o Python não confere as dicas. Use o mypy
TypeError dentro da função, numa contaUm argumento do tipo errado chegou até a conta (ex.: texto do input() sem int())
SyntaxError: expected ':' no defSeta escrita como =>; o certo é ->
NameError: name 'x' is not defined ao usar uma variável só anotadax: int sem = valor não cria a variável
NameError … in __annotate__Um nome que não existe dentro de uma dica (ex.: inteiro no lugar de int)

Exercícios

1. A dica do parâmetro

Complete: nome é um texto.

def saudar(nome: ) -> None:

2. A seta

Complete a seta do retorno (a função devolve um float):

def media(notas: list[float]) float:

3. Lista de inteiros

Escreva a dica de "uma lista de inteiros":

idades: = [12, 35, 7]

4. A função é def dobro(n: int) -> int: return n * 2. O que acontece com dobro("ab")?

5. O que quer dizer -> float | None?

6. Qual destas ferramentas confere as dicas de tipo sem rodar o programa?

Missão final: a saudação opcional

Missão: a segunda chamada, sem nome, dá erro. Faça o parâmetro nome ser opcional, com a dica str | None e o padrão None, e devolva Olá, visitante! quando não vier nome.

def saudacao(nome) -> str:
    return f"Olá, {nome}!"


print(saudacao("Ana"))
print(saudacao())
def saudacao(nome: str | None = None) -> str:
    if nome is None:
        return "Olá, visitante!"
    return f"Olá, {nome}!"


print(saudacao("Ana"))
print(saudacao())

Resumo

  • Dicas de tipo dizem que tipo cada coisa deve ter: n: int no parâmetro, -> int no retorno, idade: int = 30 na variável.
  • O Python não confere as dicas. Com o tipo errado, o programa roda (e às vezes dá um resultado errado ou um TypeError lá dentro).
  • Coleções: list[float], dict[str, float], tuple[str, int]. "Ou": float | None, str | None = None.
  • Quem confere é uma ferramenta como o mypy (ou o seu editor de código), que lê o arquivo sem rodá-lo.
  • As dicas ficam em funcao.__annotations__.
  • 3.14 As dicas agora são calculadas só quando alguém pede: dá para usar um nome definido mais adiante, sem aspas. No 3.13, escreva esse nome entre aspas.