Fundamentos e Sintaxe
Python é uma linguagem interpretada, dinamicamente tipada e de sintaxe enxuta. A partir do Python 3.12, o tipamento por anotações é nativo, f-strings aceitam qualquer expressão e match/case oferece pattern matching estrutural.
# Variáveis e anotações de tipo (PEP 604 — union com |)
nome: str = "Kaique"
idade: int = 28
ativo: bool = True
altura: float = 1.78
apelido: str | None = None # union type moderno
# f-strings com expressões e formatação
saudacao = f"Olá, {nome.upper()}! Você tem {idade} anos."
valor = 1234.5678
print(f"Preço: R$ {valor:,.2f}") # Preço: R$ 1,234.57
# Controle de fluxo — if/elif/else
def classificar(nota: float) -> str:
if nota >= 9:
return "Excelente"
elif nota >= 7:
return "Aprovado"
elif nota >= 5:
return "Recuperação"
else:
return "Reprovado"
# match/case (PEP 634) — pattern matching
def descrever(valor: object) -> str:
match valor:
case int() | float() if valor > 0:
return f"Número positivo: {valor}"
case [x, y]:
return f"Par: {x}, {y}"
case {"tipo": "user", "nome": str(n)}:
return f"Usuário {n}"
case _:
return "Desconhecido"
# Funções com tipos, defaults e keyword-only
def criar_usuario(nome: str, *, email: str, idade: int = 18) -> dict:
return {"nome": nome, "email": email, "idade": idade}
user = criar_usuario("Ana", email="[email protected]")
# Escopo — LEGB (Local, Enclosing, Global, Built-in)
contador = 0
def incrementar() -> None:
global contador
contador += 1mypy e pyright as usam para encontrar bugs antes da execução. Em projetos sérios, sempre tipe funções públicas e modelos de dados.Estruturas de Dados
Python oferece quatro estruturas nativas poderosas: listas (mutáveis, ordenadas), tuplas (imutáveis), dicionários (hash maps) e conjuntos (sets). Combinadas com comprehensions, substituem a maior parte dos loops manuais.
# Listas — mutáveis e ordenadas
numeros: list[int] = [1, 2, 3, 4, 5]
numeros.append(6)
numeros[0] = 10
primeiros = numeros[:3] # slicing: [10, 2, 3]
# Tuplas — imutáveis, ótimas como chaves de dict
coordenada: tuple[float, float] = (-23.55, -46.63)
rgb: tuple[int, int, int] = (255, 212, 59)
# Dicionários — hash map tipado
usuario: dict[str, str | int] = {
"nome": "Kaique",
"email": "[email protected]",
"idade": 28,
}
# Acesso seguro com .get() e default
pais = usuario.get("pais", "BR")
# Sets — únicos e hashable
tags: set[str] = {"python", "backend", "api"}
tags.add("async")
duplicados = {1, 2, 2, 3, 3, 3} # -> {1, 2, 3}
# List comprehension
quadrados = [n ** 2 for n in range(10) if n % 2 == 0]
# -> [0, 4, 16, 36, 64]
# Dict comprehension
indice = {user["email"]: user for user in usuarios}
# Set comprehension
dominios = {email.split("@")[1] for email in emails}
# Generator expression — lazy, sem materializar em memória
soma = sum(n * n for n in range(1_000_000))
# Unpacking e destructuring
primeiro, *meio, ultimo = [1, 2, 3, 4, 5]
# primeiro=1, meio=[2,3,4], ultimo=5
nome, email = usuario["nome"], usuario["email"]
# Merge de dicts (Python 3.9+)
base = {"host": "localhost", "port": 5432}
completo = base | {"user": "admin"} # novo dict
base |= {"ssl": True} # in-place
# Iteração idiomática
for idx, valor in enumerate(lista):
print(idx, valor)
for chave, valor in dicionario.items():
print(chave, valor)
for a, b in zip(lista_a, lista_b, strict=True):
print(a, b)dict.get(chave, default) em vez de checar if chave in dict antes de acessar. É mais Pythonic e evita dupla lookup no hash map.Coleções Especializadas (módulo collections)
defaultdict
Dict com fábrica de default automática. Ideal para agrupamentos e contadores.
Counter
Conta ocorrências de itens iteráveis. Counter("banana") retorna frequências.
deque
Fila dupla O(1) em ambas as pontas. Perfeita para buffers e BFS.
NamedTuple
Tupla com campos nomeados. Versão tipada via typing.NamedTuple.
Programação Funcional e POO
Python é multiparadigma: combina funções de primeira classe, lambdas e imutabilidade com classes, herança e dunder methods. Dataclasses eliminam boilerplate para objetos de dados.
from dataclasses import dataclass, field
from functools import reduce
# Lambda e higher-order functions
dobrar = lambda x: x * 2
numeros = [1, 2, 3, 4, 5]
dobrados = list(map(lambda n: n * 2, numeros)) # [2, 4, 6, 8, 10]
pares = list(filter(lambda n: n % 2 == 0, numeros)) # [2, 4]
total = reduce(lambda acc, n: acc + n, numeros, 0) # 15
# Em Python idiomático, comprehensions > map/filter
dobrados = [n * 2 for n in numeros]
pares = [n for n in numeros if n % 2 == 0]
# Dataclass — objeto de dados sem boilerplate
@dataclass(frozen=True, slots=True)
class Ponto:
x: float
y: float
def distancia(self, outro: "Ponto") -> float:
return ((self.x - outro.x) ** 2 + (self.y - outro.y) ** 2) ** 0.5
p1 = Ponto(0, 0)
p2 = Ponto(3, 4)
print(p1.distancia(p2)) # 5.0
# Dataclass com default_factory (evita mutable defaults)
@dataclass
class Carrinho:
itens: list[str] = field(default_factory=list)
desconto: float = 0.0
# Classe tradicional com dunder methods
class Dinheiro:
def __init__(self, valor: float, moeda: str = "BRL") -> None:
self.valor = valor
self.moeda = moeda
def __repr__(self) -> str:
return f"Dinheiro({self.valor!r}, {self.moeda!r})"
def __str__(self) -> str:
return f"{self.moeda} {self.valor:,.2f}"
def __add__(self, outro: "Dinheiro") -> "Dinheiro":
if self.moeda != outro.moeda:
raise ValueError("Moedas diferentes")
return Dinheiro(self.valor + outro.valor, self.moeda)
def __eq__(self, outro: object) -> bool:
if not isinstance(outro, Dinheiro):
return NotImplemented
return self.valor == outro.valor and self.moeda == outro.moeda
# Herança e polimorfismo
class Animal:
def __init__(self, nome: str) -> None:
self.nome = nome
def falar(self) -> str:
raise NotImplementedError
class Cachorro(Animal):
def falar(self) -> str:
return f"{self.nome}: Au!"
class Gato(Animal):
def falar(self) -> str:
return f"{self.nome}: Miau!"
animais: list[Animal] = [Cachorro("Rex"), Gato("Mia")]
for a in animais:
print(a.falar())@dataclass para classes que só guardam dados. Com frozen=True você ganha imutabilidade e hashability de graça. slots=True reduz o uso de memória em cerca de 40%.Módulos, Pacotes e Ambiente
Gerenciar dependências e ambientes é o ponto em que muitos devs tropeçam. A boa notícia: o Python moderno tem ferramentas excelentes — venv nativo, pip, e gerenciadores de próxima geração como uv e poetry.
# Criando um ambiente virtual isolado
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows PowerShell
# Instalando dependências
pip install fastapi "pydantic>=2" httpx
pip freeze > requirements.txt
# uv — gerenciador moderno (10-100x mais rápido que pip)
# Instalar: curl -LsSf https://astral.sh/uv/install.sh | sh
uv init meu-projeto
uv add fastapi pydantic httpx
uv add --dev pytest ruff mypy
uv run python app.py
# pyproject.toml — arquivo canônico (PEP 621)
[project]
name = "meu-projeto"
version = "0.1.0"
description = "API moderna em Python"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.110",
"pydantic>=2.5",
"httpx>=0.27",
]
[project.optional-dependencies]
dev = ["pytest>=8", "ruff>=0.4", "mypy>=1.10"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.ruff]
line-length = 100
target-version = "py312"# Imports e estrutura de pacotes
# projeto/
# |- src/
# | |- meu_pacote/
# | |- __init__.py
# | |- models.py
# | |- services/
# | | |- __init__.py
# | | |- user_service.py
# | |- utils/
# | |- logger.py
# |- pyproject.toml
# Import absoluto (recomendado)
from meu_pacote.models import User
from meu_pacote.services.user_service import create_user
# Import relativo (dentro do mesmo pacote)
from .models import User
from ..utils.logger import get_logger
# __init__.py como fachada do pacote
# src/meu_pacote/__init__.py
from .models import User, Product
from .services.user_service import create_user
__all__ = ["User", "Product", "create_user"]
__version__ = "0.1.0"
# if __name__ == "__main__" — script vs módulo
def main() -> None:
print("Executando como script")
if __name__ == "__main__":
main()uv em projetos novos. É escrito em Rust, resolve dependências em segundos, substitui pip, pip-tools, pipx, poetry e virtualenv em uma única ferramenta.Comparação de Gerenciadores
pip + venv
Padrão do Python. Simples, sempre disponível, mas não resolve dependências transitivas de forma determinística.
poetry
Lockfile, resolução determinística, publicação no PyPI. Maduro, mas um pouco lento em grandes projetos.
uv (Astral)
Estado da arte. Ultra-rápido, compatível com pyproject.toml, substitui todo o stack de empacotamento.
pipx
Instala CLIs Python em ambientes isolados. Ideal para ruff, black, poetry.
Async, Generators e Decorators
Três recursos avançados que elevam código Python: generators para iteração lazy, decorators para composição transparente e asyncio para concorrência I/O sem threads.
import asyncio
import httpx
from functools import wraps
from contextlib import contextmanager
from time import perf_counter
# Generators — lazy evaluation com yield
def contador(inicio: int, fim: int):
atual = inicio
while atual < fim:
yield atual
atual += 1
for n in contador(0, 5):
print(n) # 0, 1, 2, 3, 4
# Generator de arquivo — processa GB sem carregar tudo
def ler_linhas(caminho: str):
with open(caminho, encoding="utf-8") as f:
for linha in f:
yield linha.rstrip()
# yield from — delega a outro iterável
def achatar(listas: list[list[int]]):
for sublista in listas:
yield from sublista
# Async/await — concorrência I/O
async def buscar_url(client: httpx.AsyncClient, url: str) -> dict:
resposta = await client.get(url, timeout=10.0)
resposta.raise_for_status()
return resposta.json()
async def buscar_varias(urls: list[str]) -> list[dict]:
async with httpx.AsyncClient() as client:
tarefas = [buscar_url(client, url) for url in urls]
return await asyncio.gather(*tarefas, return_exceptions=True)
# Executar corrotina
resultados = asyncio.run(buscar_varias([
"https://api.github.com",
"https://httpbin.org/json",
]))
# asyncio.TaskGroup (Python 3.11+) — concorrência estruturada
async def processar(client: httpx.AsyncClient) -> None:
async with asyncio.TaskGroup() as tg:
tarefa1 = tg.create_task(buscar_url(client, "https://httpbin.org/get"))
tarefa2 = tg.create_task(buscar_url(client, "https://httpbin.org/ip"))
# Aqui ambas terminaram; exceção em qualquer uma cancela todas
# Decorator básico — medir tempo
def medir_tempo(func):
@wraps(func)
def wrapper(*args, **kwargs):
inicio = perf_counter()
resultado = func(*args, **kwargs)
duracao = perf_counter() - inicio
print(f"{func.__name__} levou {duracao:.3f}s")
return resultado
return wrapper
@medir_tempo
def processar_dados(n: int) -> int:
return sum(i * i for i in range(n))
# Decorator com argumentos — cache com TTL
def cache_com_ttl(ttl_segundos: int):
def decorator(func):
cache: dict = {}
@wraps(func)
def wrapper(*args, **kwargs):
chave = (args, tuple(sorted(kwargs.items())))
agora = perf_counter()
if chave in cache:
valor, timestamp = cache[chave]
if agora - timestamp < ttl_segundos:
return valor
valor = func(*args, **kwargs)
cache[chave] = (valor, agora)
return valor
return wrapper
return decorator
@cache_com_ttl(ttl_segundos=60)
def consulta_cara(user_id: int) -> dict:
return {"id": user_id, "dados": "..."}
# Context manager com @contextmanager
@contextmanager
def cronometro(nome: str):
inicio = perf_counter()
try:
yield
finally:
print(f"{nome}: {perf_counter() - inicio:.3f}s")
with cronometro("processamento"):
processar_dados(1_000_000)
# Context manager como classe
class ConexaoDB:
def __enter__(self):
self.conn = abrir_conexao()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
return False # não suprime exceçõesmultiprocessing ou concurrent.futures.ProcessPoolExecutor. Async brilha em rede, disco e bancos — operações onde o processo fica esperando.time.sleep() em código async. Ele bloqueia toda a event loop. Use await asyncio.sleep(n) em vez disso.Ecossistema Python
O verdadeiro poder de Python está nas bibliotecas. Do backend web (FastAPI) aos dados (Pandas/Polars), passando por qualidade de código (ruff, mypy) e testes (pytest), o ecossistema cobre praticamente todo problema.
# FastAPI + Pydantic — API tipada e auto-documentada
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
class UserCreate(BaseModel):
nome: str = Field(min_length=2, max_length=100)
email: EmailStr
idade: int = Field(ge=18, le=120)
class UserResponse(BaseModel):
id: int
nome: str
email: EmailStr
criado_em: datetime
app = FastAPI(title="API de Usuários", version="1.0.0")
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def criar_usuario(payload: UserCreate) -> UserResponse:
# Validação já garantida pelo Pydantic
novo_id = await repo.criar(payload.model_dump())
return UserResponse(
id=novo_id,
nome=payload.nome,
email=payload.email,
criado_em=datetime.utcnow(),
)
@app.get("/users/{user_id}", response_model=UserResponse)
async def buscar_usuario(user_id: int) -> UserResponse:
user = await repo.buscar(user_id)
if user is None:
raise HTTPException(status_code=404, detail="Usuário não encontrado")
return user
# uvicorn app:app --reload (Swagger em /docs)# Pandas — análise de dados tabulares
import pandas as pd
df = pd.read_csv("vendas.csv", parse_dates=["data"])
# Filtrar, agrupar e agregar
resumo = (
df[df["valor"] > 100]
.groupby("categoria")
.agg(
total=("valor", "sum"),
media=("valor", "mean"),
count=("valor", "size"),
)
.sort_values("total", ascending=False)
)
# Polars — alternativa moderna em Rust, até 30x mais rápido
import polars as pl
df = pl.read_csv("vendas.csv")
resumo = (
df.filter(pl.col("valor") > 100)
.group_by("categoria")
.agg([
pl.col("valor").sum().alias("total"),
pl.col("valor").mean().alias("media"),
pl.len().alias("count"),
])
.sort("total", descending=True)
)# pytest — testes expressivos com fixtures
import pytest
from app.services import calcular_desconto
class TestDesconto:
def test_sem_desconto_abaixo_de_100(self):
assert calcular_desconto(50) == 0
@pytest.mark.parametrize("valor,esperado", [
(100, 10),
(200, 20),
(1000, 100),
])
def test_desconto_de_10_porcento(self, valor, esperado):
assert calcular_desconto(valor) == esperado
def test_valor_negativo_levanta_erro(self):
with pytest.raises(ValueError, match="não pode ser negativo"):
calcular_desconto(-1)
# Fixture reutilizável
@pytest.fixture
def client_api():
from fastapi.testclient import TestClient
from app.main import app
return TestClient(app)
def test_endpoint_users(client_api):
resposta = client_api.post("/users", json={
"nome": "Ana", "email": "[email protected]", "idade": 30
})
assert resposta.status_code == 201
assert resposta.json()["nome"] == "Ana"# ruff — linter + formatter ultra-rápido (substitui black+flake8+isort)
# Configuração em pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "RUF"]
ignore = ["E501"]
# CLI
# ruff check . # lint
# ruff check --fix . # auto-fix
# ruff format . # format
# mypy — type checker estático
[tool.mypy]
python_version = "3.12"
strict = true
warn_unused_ignores = true
warn_redundant_casts = true
# mypy src/ -> reporta erros de tipo antes do runtimeBibliotecas Essenciais
FastAPI
Framework web async baseado em Starlette. Tipado, rápido, OpenAPI automático.
Pydantic v2
Validação de dados e serialização via type hints. Core em Rust, extremamente performático.
httpx
Cliente HTTP sync + async compatível com requests. Suporte a HTTP/2.
SQLAlchemy 2.0
ORM + Core SQL. API moderna com type hints e suporte async nativo.
Polars
DataFrame em Rust. Alternativa ao Pandas, 10-30x mais rápido com API mais limpa.
pytest + coverage
Framework de testes de facto. Fixtures, parametrização, plugins para tudo.
ruff (Astral)
Linter/formatter em Rust. 100x mais rápido que flake8, substitui black/isort/pyupgrade.
rich / textual
Terminal bonito: tabelas, progresso, sintaxe colorida. Textual constrói TUIs completas.
uv + FastAPI + Pydantic v2 + SQLAlchemy 2 async + httpx + pytest + ruff + mypy. Deploy com uvicorn atrás de nginx ou em container.