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.
- 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: "
ndeve ser umint" (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 oreturn(aula return). Lê-se "devolve umint". - :
- O dois-pontos do fim do
defcontinua 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
| Dica | Quer dizer | Exemplo de valor |
|---|---|---|
int | Número inteiro | 3 |
float | Número com casas decimais (aceita inteiros também) | 2.5 |
str | Texto | "Ana" |
bool | True ou False | True |
None | Nada (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 tipofloat(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).
| Dica | Quer dizer | Exemplo 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"} |
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
floatouNone." Quem chama a função fica avisado: o resultado pode não ser um número, e é preciso tratar esse caso (com umif 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".
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
dobrotem o tipo incompatívelstr; esperavaint." Exatamente o aviso que o Python, sozinho, não deu: odobro("ab")rodou calado. - checar.py:10: … Argument 2 to "total"
- O
"3"dototal, 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
idadefoi anunciada comointe recebeu um texto. O Python aceitaria sem reclamar. - Success: no issues found
- "Sucesso: nenhum problema encontrado" nos arquivos
saudacao.pyebusca.py, que usam as dicas direito.
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.
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.
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 parainteiro.- NameError: name 'inteiro' is not defined
- "O nome
inteironão está definido." O tipo dos inteiros, em Python, se chamaint.
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
| Forma | Quando usar |
|---|---|
| Sem dicas | Programas pequenos e rápidos, como os exercícios das aulas. É Python válido e muito comum |
| Os tipos só na docstring | Funciona para quem lê, mas o editor de código e o mypy não entendem texto livre |
| Dicas de tipo + docstring | Funçõ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
=> 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:.
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
| Escrita | O 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 = 30 | Variá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 | None | Um float ou None |
nome: str | None = None | Parâmetro opcional |
f.__annotations__ | O dicionário com as dicas da função |
mypy arquivo.py | Confere as dicas sem rodar (instale com pip numa venv) |
| Sintoma | Causa provável |
|---|---|
| O programa roda com o tipo errado, sem aviso | Normal: o Python não confere as dicas. Use o mypy |
TypeError dentro da função, numa conta | Um argumento do tipo errado chegou até a conta (ex.: texto do input() sem int()) |
SyntaxError: expected ':' no def | Seta escrita como =>; o certo é -> |
NameError: name 'x' is not defined ao usar uma variável só anotada | x: 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.
2. A seta
Complete a seta do retorno (a função devolve um float):
3. Lista de inteiros
Escreva a dica de "uma lista de inteiros":
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: intno parâmetro,-> intno retorno,idade: int = 30na 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
TypeErrorlá 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.