Introdução aos tipos Python¶
O Python possui suporte para "dicas de tipo" ou "type hints" (também chamado de "anotações de tipo" ou "type annotations")
Esses "type hints" são uma sintaxe especial que permite declarar o tipo de uma variável.
Ao declarar tipos para suas variáveis, editores e ferramentas podem oferecer um melhor suporte.
Este é apenas um tutorial rápido / atualização sobre type hints do Python. Ele cobre apenas o mínimo necessário para usá-los com o FastAPI... que é realmente muito pouco.
O FastAPI é baseado nesses type hints, eles oferecem muitas vantagens e benefícios.
Mas mesmo que você nunca use o FastAPI, você se beneficiaria de aprender um pouco sobre eles.
Nota
Se você é um especialista em Python e já sabe tudo sobre type hints, pule para o próximo capítulo.
Motivação¶
Vamos começar com um exemplo simples:
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
A chamada deste programa gera:
John Doe
A função faz o seguinte:
- Pega um
first_nameelast_name. - Converte a primeira letra de cada uma em maiúsculas com
title(). - Concatena com um espaço no meio.
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Edite-o¶
É um programa muito simples.
Mas agora imagine que você estava escrevendo do zero.
Em algum momento você teria iniciado a definição da função, já tinha os parâmetros prontos...
Mas então você deve chamar "esse método que converte a primeira letra em maiúscula".
Era upper? Era uppercase? first_uppercase? capitalize?
Em seguida, tente com o velho amigo do programador, o preenchimento automático do editor.
Você digita o primeiro parâmetro da função, first_name, depois um ponto (.) e, em seguida, pressiona Ctrl + Space para acionar a conclusão.
Mas, infelizmente, você não obtém nada útil:

Adicionar tipos¶
Vamos modificar uma única linha da versão anterior.
Vamos mudar exatamente esse fragmento, os parâmetros da função, de:
first_name, last_name
para:
first_name: str, last_name: str
É isso aí.
Esses são os "type hints":
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Isso não é o mesmo que declarar valores padrão como seria com:
first_name="john", last_name="doe"
É uma coisa diferente.
Estamos usando dois pontos (:), não é igual a (=).
E adicionar type hints normalmente não muda o que acontece do que aconteceria sem eles.
Mas agora, imagine que você está novamente no meio da criação dessa função, mas com type hints.
No mesmo ponto, você tenta acionar o preenchimento automático com o Ctrl+Space e vê:

Com isso, você pode rolar, vendo as opções, até encontrar o que "soa familiar":

Mais motivação¶
Verifique esta função, ela já possui type hints:
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
Como o editor conhece os tipos de variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro:

Agora você sabe que precisa corrigí-lo, converta age em uma string com str(age):
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
Declarando Tipos¶
Você acabou de ver o local principal para declarar type hints. Como parâmetros de função.
Este também é o principal local em que você os usaria com o FastAPI.
Tipos simples¶
Você pode declarar todos os tipos padrão de Python, não apenas str.
Você pode usar, por exemplo:
intfloatboolbytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
Tipos genéricos com parâmetros de tipo¶
Existem algumas estruturas de dados que podem conter outros valores, como dict, list, set e tuple. E os valores internos também podem ter seu próprio tipo.
Estes tipos que possuem tipos internos são chamados de tipos "genéricos". E é possível declará-los mesmo com os seus tipos internos.
Para declarar esses tipos e os tipos internos, você pode usar o módulo Python padrão typing. Ele existe especificamente para suportar esses type hints.
Versões mais recentes do Python¶
A sintaxe utilizando typing é compatível com todas as versões, desde o Python 3.6 até as últimas, incluindo o Python 3.9, 3.10, etc.
Conforme o Python evolui, novas versões chegam com suporte melhorado para esses type annotations, e em muitos casos, você não precisará nem importar e utilizar o módulo typing para declarar os type annotations.
Se você pode escolher uma versão mais recente do Python para o seu projeto, você poderá aproveitar isso ao seu favor.
Em todos os documentos existem exemplos compatíveis com cada versão do Python (quando existem diferenças).
Por exemplo, "Python 3.6+" significa que é compatível com o Python 3.6 ou superior (incluindo o 3.7, 3.8, 3.9, 3.10, etc). E "Python 3.9+" significa que é compatível com o Python 3.9 ou mais recente (incluindo o 3.10, etc).
Se você pode utilizar a versão mais recente do Python, utilize os exemplos para as últimas versões. Eles terão as melhores e mais simples sintaxes, como por exemplo, "Python 3.10+".
List¶
Por exemplo, vamos definir uma variável para ser uma list de str.
Declare uma variável com a mesma sintaxe com dois pontos (:)
Como tipo, coloque list.
Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes:
def process_items(items: list[str]):
for item in items:
print(item)
De typing, importe List (com o L maiúsculo):
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Declare uma variável com a mesma sintaxe com dois pontos (:)
Como tipo, coloque o List que você importou de typing.
Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Informação
Estes tipos internos dentro dos colchetes são chamados "parâmetros de tipo" (type parameters).
Neste caso, str é o parâmetro de tipo passado para List (ou list no Python 3.9 ou superior).
Isso significa: "a variável items é uma list, e cada um dos itens desta lista é uma str".
Dica
Se você usa o Python 3.9 ou superior, você não precisa importar List de typing. Você pode utilizar o mesmo tipo list no lugar.
Ao fazer isso, seu editor pode fornecer suporte mesmo durante o processamento de itens da lista:

Sem tipos, isso é quase impossível de alcançar.
Observe que a variável item é um dos elementos da lista items.
E, ainda assim, o editor sabe que é um str e fornece suporte para isso.
Tuple e Set¶
Você faria o mesmo para declarar tuples e sets:
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
from typing import Set, Tuple
def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
return items_t, items_s
Isso significa que:
- A variável
items_té umatuplecom 3 itens, umint, outrointe umastr. - A variável
items_sé umset, e cada um de seus itens é do tipobytes.
Dict¶
Para definir um dict, você passa 2 parâmetros de tipo, separados por vírgulas.
O primeiro parâmetro de tipo é para as chaves do dict.
O segundo parâmetro de tipo é para os valores do dict:
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
from typing import Dict
def process_items(prices: Dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
Isso significa que:
- A variável
pricesé um dict`:- As chaves deste
dictsão do tipostr(digamos, o nome de cada item). - Os valores deste
dictsão do tipofloat(digamos, o preço de cada item).
- As chaves deste
Union¶
Você pode declarar que uma variável pode ser de qualquer um dentre diversos tipos. Por exemplo, um int ou um str.
No Python 3.6 e superior (incluindo o Python 3.10), você pode utilizar o tipo Union de typing, e colocar dentro dos colchetes os possíveis tipos aceitáveis.
No Python 3.10 também existe uma nova sintaxe onde você pode colocar os possívels tipos separados por uma barra vertical (|).
def process_item(item: int | str):
print(item)
from typing import Union
def process_item(item: Union[int, str]):
print(item)
Em ambos os casos, isso significa que item poderia ser um int ou um str.
Possívelmente None¶
Você pode declarar que um valor pode ter um tipo, como str, mas que ele também pode ser None.
No Python 3.6 e superior (incluindo o Python 3.10) você pode declará-lo importando e utilizando Optional do módulo typing.
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
O uso de Optional[str] em vez de apenas str permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um str, quando na verdade também pode ser None.
Optional[Something] é na verdade um atalho para Union[Something, None], eles são equivalentes.
Isso também significa que no Python 3.10, você pode utilizar Something | None:
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Utilizando Union ou Optional¶
Se você está utilizando uma versão do Python abaixo da 3.10, aqui vai uma dica do meu ponto de vista bem subjetivo:
- 🚨 Evite utilizar
Optional[SomeType] - No lugar, ✨ use
Union[SomeType, None]✨.
Ambos são equivalentes, e no final das contas, eles são o mesmo. Mas eu recomendaria o Union ao invés de Optional porque a palavra Optional parece implicar que o valor é opcional, quando na verdade significa "isso pode ser None", mesmo que ele não seja opcional e ainda seja obrigatório.
Eu penso que Union[SomeType, None] é mais explícito sobre o que ele significa.
Isso é apenas sobre palavras e nomes. Mas estas palavras podem afetar como os seus colegas de trabalho pensam sobre o código.
Por exemplo, vamos pegar esta função:
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
🤓 Other versions and variants
def say_hi(name: str | None):
print(f"Hey {name}!")
O paâmetro name é definido como Optional[str], mas ele não é opcional, você não pode chamar a função sem o parâmetro:
say_hi() # Oh, no, this throws an error! 😱
O parâmetro name ainda é obrigatório (não opicional) porque ele não possui um valor padrão. Mesmo assim, name aceita None como valor:
say_hi(name=None) # This works, None is valid 🎉
A boa notícia é, quando você estiver no Python 3.10 você não precisará se preocupar mais com isso, pois você poderá simplesmente utilizar o | para definir uniões de tipos:
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 Other versions and variants
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
E então você não precisará mais se preocupar com nomes como Optional e Union. 😎
Tipos genéricos¶
Esses tipos que usam parâmetros de tipo entre colchetes são chamados tipos genéricos ou genéricos. Por exemplo:
Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro):
listtuplesetdict
E o mesmo como no Python 3.8, do módulo typing:
UnionOptional(o mesmo que com o 3.8)- ...entro outros.
No Python 3.10, como uma alternativa para a utilização dos genéricos Union e Optional, você pode usar a barra vertical (|) para declarar uniões de tipos. Isso é muito melhor e mais simples.
Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro):
listtuplesetdict
E o mesmo como no Python 3.8, do módulo typing:
UnionOptional- ...entro outros.
ListTupleSetDictUnionOptional- ...entro outros.
Classes como tipos¶
Você também pode declarar uma classe como o tipo de uma variável.
Digamos que você tenha uma classe Person, com um nome:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
Então você pode declarar que uma variável é do tipo Person:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
E então, novamente, você recebe todo o suporte do editor:

Perceba que isso significa que "one_person é uma instância da classe Person".
Isso não significa que "one_person é a classe chamada Person".
Modelos Pydantic¶
O Pydantic é uma biblioteca Python para executar a validação de dados.
Você declara a "forma" dos dados como classes com atributos.
E cada atributo tem um tipo.
Em seguida, você cria uma instância dessa classe com alguns valores e ela os validará, os converterá para o tipo apropriado (se for esse o caso) e fornecerá um objeto com todos os dados.
E você recebe todo o suporte do editor com esse objeto resultante.
Retirado dos documentos oficiais dos Pydantic:
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: List[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
Informação
Para saber mais sobre o Pydantic, verifique a sua documentação.
O FastAPI é todo baseado em Pydantic.
Você verá muito mais disso na prática no Tutorial - Guia do usuário.
Dica
O Pydantic tem um comportamento especial quando você usa Optional ou Union[Something, None] sem um valor padrão. Você pode ler mais sobre isso na documentação do Pydantic sobre campos Opcionais Obrigatórios.
Type Hints com Metadados de Anotações¶
O Python possui uma funcionalidade que nos permite incluir metadados adicionais nos type hints utilizando Annotated.
No Python 3.9, Annotated é parte da biblioteca padrão, então você pode importá-lo de typing.
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
Em versões abaixo do Python 3.9, você importa Annotated de typing_extensions.
Ele já estará instalado com o FastAPI.
from typing_extensions import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
O Python em si não faz nada com este Annotated. E para editores e outras ferramentas, o tipo ainda é str.
Mas você pode utilizar este espaço dentro do Annotated para fornecer ao FastAPI metadata adicional sobre como você deseja que a sua aplicação se comporte.
O importante aqui de se lembrar é que o primeiro type parameter que você informar ao Annotated é o tipo de fato. O resto é apenas metadado para outras ferramentas.
Por hora, você precisa apenas saber que o Annotated existe, e que ele é Python padrão. 😎
Mais tarde você verá o quão poderoso ele pode ser.
Dica
O fato de que isso é Python padrão significa que você ainda obtém a melhor experiência de desenvolvedor possível no seu editor, com as ferramentas que você utiliza para analisar e refatorar o seu código, etc. ✨
E também que o seu código será muito compatível com diversas outras ferramentas e bibliotecas Python. 🚀
Type hints no FastAPI¶
O FastAPI aproveita esses type hints para fazer várias coisas.
Com o FastAPI, você declara parâmetros com type hints e obtém:
- Suporte ao editor.
- Verificações de tipo.
... e o FastAPI usa as mesmas declarações para:
- Definir requisitos: dos parâmetros de rota, parâmetros da consulta, cabeçalhos, corpos, dependências, etc.
- Converter dados: da solicitação para o tipo necessário.
- Validar dados: provenientes de cada solicitação:
- Gerando erros automáticos retornados ao cliente quando os dados são inválidos.
- Documentar a API usando OpenAPI:
- que é usado pelas interfaces de usuário da documentação interativa automática.
Tudo isso pode parecer abstrato. Não se preocupe. Você verá tudo isso em ação no Tutorial - Guia do usuário.
O importante é que, usando tipos padrão de Python, em um único local (em vez de adicionar mais classes, decoradores, etc.), o FastAPI fará muito trabalho para você.
Informação
Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do mypy .