DevTools

Cheatsheet Python

Linguagem de programação versátil e fácil de aprender

Voltar às linguagens
Python
109 cards encontrados
Categorias:
Versões:

Sintaxe Básica


12 cards
Variáveis e Atribuição
nome = "Ana"
idade = 30
preco = 9.99
ativo = True

# Atribuição múltipla
x, y, z = 1, 2, 3
a = b = c = 0

# Swap elegante
x, y = y, x

# Type hints (opcional)
nome: str = "Ana"
idade: int = 30

Python tem tipagem dinâmica: não é preciso declarar o tipo. Atribuição múltipla com vírgulas. x, y = y, x faz swap sem variável temporária. Type hints são opcionais e não forçados em runtime.

Operadores
# Aritméticos
10 + 3   # 13
10 - 3   # 7
10 * 3   # 30
10 / 3   # 3.333 (float)
10 // 3  # 3 (divisão inteira)
10 % 3   # 1 (resto)
2 ** 10  # 1024 (potência)

# Comparação
5 == 5   # True
5 != 3   # True
5 > 3    # True

# Lógicos
True and False  # False
True or False   # True
not True        # False

# Identidade e pertença
x is None       # True/False
5 in [1, 2, 5]  # True

/ devolve sempre float; // faz divisão inteira. ** é potência. is compara identidade (mesmo objeto), == compara valor. in verifica pertença.

Comentários e Documentação
# Comentário de linha

"""
Docstring multi-linha
para módulos, classes e funções.
"""

def soma(a: int, b: int) -> int:
    """Soma dois números.

    Args:
        a: Primeiro número
        b: Segundo número

    Returns:
        A soma de a e b
    """
    return a + b

# Aceder docstring
help(soma)
print(soma.__doc__)

# para comentários. """...""" (docstring) documenta funções/classes. Convenção: usar formato Google style ou NumPy style. help() mostra a docstring. Ferramentas como Sphinx geram docs automáticos.

Tipos de Dados
# Tipos primitivos
inteiro = 10        # int
decimal = 3.14      # float
complexo = 2 + 3j   # complex
texto = "Olá"       # str
ativo = True        # bool
vazio = None        # NoneType

# Coleções
lista = [1, 2, 3]   # list
tuplo = (1, 2, 3)   # tuple
dicio = {"a": 1}    # dict
conj  = {1, 2, 3}   # set

# Verificar tipo
type(42)            # <class 'int'>
isinstance(42, int) # True

Tipos embutidos: int, float, str, bool, NoneType. Coleções: list, tuple, dict, set. Use type() ou isinstance() para verificar.

Conversões (Casting)
int("42")       # 42
float("3.14")   # 3.14
str(100)        # "100"
bool(0)         # False
bool("texto")   # True

list("abc")     # ['a', 'b', 'c']
tuple([1, 2])   # (1, 2)
set([1, 1, 2])  # {1, 2}
dict([("a", 1)])  # {"a": 1}

int(3.9)        # 3 (trunca!)
round(3.9)      # 4
round(3.14159, 2)  # 3.14

Conversões: int(), float(), str(), bool(). int() trunca decimais (não arredonda). round() arredonda. Valores falsy: 0, "", [], None, False.

Operadores de Identidade e Pertença
# is / is not (identidade)
a = [1, 2, 3]
b = [1, 2, 3]
a == b      # True (mesmo valor)
a is b      # False (objetos diferentes)

c = a
c is a      # True (mesmo objeto)

# in / not in (pertença)
5 in [1, 2, 5]        # True
"x" not in "texto"    # False
"a" in {"a": 1}       # True (chave)
3 in (1, 2, 3)        # True

# None: sempre com is
if valor is None:
    pass
if valor is not None:
    pass

is verifica se são o mesmo objeto (identidade). == compara valores. Use is None para None. in/not in verifica pertença em listas, dicts, strings, tuples e sets.

Strings — Métodos
s = "  Olá Mundo  "

s.strip()            # "Olá Mundo"
s.upper()            # "  OLÁ MUNDO  "
s.lower()            # "  olá mundo  "
s.title()            # "  Olá Mundo  "
s.split()            # ["Olá", "Mundo"]
s.replace("Mundo", "Python")
s.startswith("Olá")  # False (tem espaços)
s.find("Mundo")      # 6 (índice)
s.count("o")         # 2
len(s)               # 13

# Concatenação e repetição
"Olá" + " " + "Mundo"
"ha" * 3             # "hahaha"

Strings são imutáveis: métodos devolvem nova string. strip() remove espaços. split() divide em lista. find() retorna índice (-1 se não encontrar). len() dá o comprimento.

Entrada e Saída
# Saída
print("Olá", "Mundo")         # Olá Mundo
print("a", "b", sep="-")      # a-b
print("sem quebra", end=" ")  # sem \n
print(f"Nome: {nome}")

# Entrada
nome = input("Nome: ")
idade = int(input("Idade: "))

# print com lista
print([1, 2, 3])       # [1, 2, 3]
print(*[1, 2, 3])      # 1 2 3 (unpack)
print(*[1, 2, 3], sep=", ")  # 1, 2, 3

print() com sep e end personaliza output. input() retorna sempre str — use int() para converter. *lista faz unpack como argumentos separados.

Multi-linha e Expressões
# String multi-linha
texto = """
Primeira linha
Segunda linha
"""

# Expressão em várias linhas
resultado = (
    valor1
    + valor2
    + valor3
)

# Lista em várias linhas
frutas = [
    "maçã",
    "banana",
    "cereja",
]

# Backslash (evitar)
total = preco * \
        quantidade

"""...""" cria strings multi-linha. Parênteses () permitem expressões em várias linhas (preferido). Vírgula final em listas é válida (trailing comma). Evite \ para continuação.

f-strings e Formatação
nome = "Ana"
idade = 30
preco = 9.5

# f-string (recomendado)
print(f"A {nome} tem {idade} anos")
print(f"Expressão: {idade + 5}")
print(f"Método: {nome.upper()}")

# Formatação numérica
print(f"Preço: {preco:.2f}€")   # 9.50€
print(f"{0.85:.0%}")            # 85%
print(f"{1234567:,}")           # 1,234,567
print(f"{42:08d}")              # 00000042
print(f"{'centro':^20}")        # centrado

# Alternativas
"Olá {}".format(nome)
"Olá %s" % nome

f-strings (prefixo f) inserem expressões em {}. Formatação: :.2f decimais, :, milhares, :08d padding. .format() e % são alternativas antigas.

None e Truthiness
# None é o "nulo" do Python
resultado = None
if resultado is None:
    print("Sem resultado")

# Truthy vs Falsy
# Falsy: False, 0, 0.0, "", [], {}, (), set(), None
# Truthy: tudo o resto

if []:       # False (lista vazia)
    pass
if [1, 2]:   # True (lista com itens)
    pass

# Padrão: valor or default
nome = input() or "Anónimo"
valor = dados.get("x") or 0

None é o valor nulo — compare com is None, nunca == None. Valores falsy: 0, "", [], {}, None. or retorna o primeiro valor truthy.

Constantes e Convenções
# Python não tem constantes reais
# Convenção: UPPER_SNAKE_CASE
MAX_RETRIES = 3
API_URL = "https://api.exemplo.com"
PI = 3.14159

# typing.Final (3.8+) — hint para IDEs
from typing import Final
VERSION: Final = "2.0.0"
TIMEOUT: Final[int] = 30

# Enum para constantes relacionadas
from enum import Enum, auto

class Status(Enum):
    ATIVO = auto()
    INATIVO = auto()
    SUSPENSO = auto()

Status.ATIVO.value  # 1
Status.ATIVO.name   # "ATIVO"

Python não tem constantes reais — use UPPER_SNAKE_CASE por convenção. typing.Final indica à IDE que não deve ser alterado. Enum cria conjuntos de constantes relacionadas. auto() atribui valores automáticos.

Controlo de Fluxo


8 cards
if / elif / else
idade = 20

if idade >= 18:
    print("Adulto")
elif idade >= 13:
    print("Adolescente")
else:
    print("Criança")

# Sem chavetas — usa indentação!
# Operadores: and, or, not
if idade >= 18 and idade < 65:
    print("Idade ativa")

# Condição inline
status = "adulto" if idade >= 18 else "menor"

Python usa indentação (4 espaços) em vez de chavetas. elif = else if. Operadores lógicos: and, or, not. Expressão ternária: x if cond else y.

break, continue e else
# break: sai do loop
for i in range(10):
    if i == 5:
        break
    print(i)  # 0, 1, 2, 3, 4

# continue: salta iteração
for i in range(10):
    if i % 2 == 0:
        continue
    print(i)  # 1, 3, 5, 7, 9

# else no loop (executa sem break)
for n in range(2, 20):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(f"{n} é primo")

break termina o loop. continue salta para a próxima iteração. else em loops executa apenas se o loop terminar sem break — útil para buscas.

Operador Ternário
# Sintaxe: valor_se_true if condição else valor_se_false
idade = 20
status = "adulto" if idade >= 18 else "menor"

# Com expressões
resultado = x * 2 if x > 0 else 0

# Encadeado (evitar!)
nivel = "alto" if x > 100 else "médio" if x > 50 else "baixo"

# Com or (default)
nome = input() or "Anónimo"
valor = dados.get("chave") or "default"

# Em list comprehension
pares = [x for x in range(10) if x % 2 == 0]

Expressão ternária: valor if condição else default. Mais legível que if/else para casos simples. or dá default para valores falsy. Em comprehensions, o if vem no fim como filtro.

match / case (3.10+)
# Pattern matching (Python 3.10+)
def processar(comando):
    match comando.split():
        case ["sair"]:
            return "Adeus"
        case ["olá", nome]:
            return f"Olá, {nome}!"
        case ["soma", *nums]:
            return sum(int(n) for n in nums)
        case _:
            return "Comando desconhecido"

# Com tipos
match valor:
    case int():
        print("É inteiro")
    case str():
        print("É string")
    case [x, y]:
        print(f"Par: {x}, {y}")
    case _:
        print("Outro")

match/case (Python 3.10+) é pattern matching estrutural. case _ é o default. Pode fazer unpack de listas, capturar variáveis e verificar tipos. Mais poderoso que switch de outras linguagens.

for e Iteração
# Iterar lista
for fruta in ["maçã", "banana", "cereja"]:
    print(fruta)

# range
for i in range(5):       # 0, 1, 2, 3, 4
    print(i)

for i in range(2, 10, 3):  # 2, 5, 8
    print(i)

# Iterar string
for char in "Python":
    print(char)

# Iterar dict
for chave, valor in {"a": 1, "b": 2}.items():
    print(f"{chave}: {valor}")

# for com else (executa se não houver break)
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(f"{n} é primo")

for itera qualquer iterável. range(início, fim, passo) gera sequência. .items() itera chave+valor de dict. for...else: o else executa se o loop terminar sem break.

Comparações Encadeadas
# Python permite encadear comparações
x = 15
0 < x < 100        # True (equivale a x > 0 and x < 100)
1 <= x <= 20       # True

# Múltiplas
a, b, c = 1, 5, 10
a < b < c          # True
a < b > c          # False

# Com variáveis
limite_inf = 0
limite_sup = 100
if limite_inf <= x <= limite_sup:
    print("No intervalo")

# any / all com condições
nums = [2, 4, 6, 8]
all(n % 2 == 0 for n in nums)  # True
any(n > 5 for n in nums)       # True

Python permite comparações encadeadas: 0 < x < 100 é mais legível que x > 0 and x < 100. all() retorna True se todos cumprem. any() retorna True se algum cumpre.

while
# Loop com condição
contador = 0
while contador < 5:
    print(contador)
    contador += 1

# Loop infinito com break
while True:
    cmd = input("> ")
    if cmd == "sair":
        break
    processar(cmd)

# while com else
n = 10
while n > 0:
    if n == 5:
        break
    n -= 1
else:
    print("Terminou sem break")

while repete enquanto a condição for True. while True + break para loops controlados por input. while...else: o else executa se não houver break. Cuidado com loops infinitos!

Loops Aninhados
# Tabuada
for i in range(1, 11):
    for j in range(1, 11):
        print(f"{i*j:4}", end="")
    print()

# Matriz
matriz = [[1, 2], [3, 4], [5, 6]]
for linha in matriz:
    for elem in linha:
        print(elem, end=" ")
    print()

# Achatamento com comprehension
plano = [x for sub in matriz for x in sub]
# [1, 2, 3, 4, 5, 6]

# enumerate aninhado
for i, linha in enumerate(matriz):
    for j, val in enumerate(linha):
        print(f"[{i}][{j}] = {val}")

Loops aninhados para matrizes e tabelas. Em comprehensions: for externo vem primeiro. enumerate() dá índice + valor. Para achatar: [x for sub in lista for x in sub].

Funções


10 cards
Definir Funções
def saudacao(nome: str) -> str:
    """Retorna uma saudação."""
    return f"Olá, {nome}!"

# Valores por omissão
def potencia(base, expoente=2):
    return base ** expoente

potencia(3)       # 9
potencia(2, 10)   # 1024

# Múltiplos retornos (tuple)
def dividir(a, b):
    return a // b, a % b

quoc, resto = dividir(17, 5)  # 3, 2

Funções com def. Type hints opcionais (: str, -> str). Valores default nos parâmetros. Retorno múltiplo via tuple com unpack. Sem return, retorna None.

Closures e Scope
# Closure: função que "lembra" o scope
def contador():
    count = 0
    def incrementar():
        nonlocal count
        count += 1
        return count
    return incrementar

c = contador()
c()  # 1
c()  # 2
c()  # 3

# LEGB: Local, Enclosing, Global, Built-in
x = "global"
def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)
    inner()

# global
def set_global():
    global x
    x = "modificado"

Closure: função interna que captura variáveis do scope externo. nonlocal permite modificar variável do scope enclosing. Regra LEGB: Local → Enclosing → Global → Built-in. global acede à variável global.

Funções como Objetos
# Funções são first-class
def gritar(texto):
    return texto.upper()

def sussurrar(texto):
    return texto.lower()

# Passar como argumento
def executar(fn, msg):
    return fn(msg)

executar(gritar, "olá")     # "OLÁ"
executar(sussurrar, "OLÁ")  # "olá"

# Guardar em estruturas
ops = {
    "soma": lambda a, b: a + b,
    "mult": lambda a, b: a * b,
}
ops["soma"](3, 4)  # 7

# Retornar função
def multiplicador(n):
    return lambda x: x * n

dobro = multiplicador(2)
dobro(5)  # 10

Funções são objetos first-class: podem ser passadas, guardadas e retornadas. Higher-order functions recebem/retornam funções. Base de decorators, callbacks e padrões como Strategy.

*args e **kwargs
# *args: argumentos posicionais variáveis
def soma(*nums):
    return sum(nums)

soma(1, 2, 3)      # 6
soma(1, 2, 3, 4)   # 10

# **kwargs: argumentos nomeados variáveis
def perfil(**dados):
    for chave, valor in dados.items():
        print(f"{chave}: {valor}")

perfil(nome="Ana", idade=30)

# Combinar
def func(*args, **kwargs):
    print(args)    # tuple
    print(kwargs)  # dict

# Unpacking
lista = [1, 2, 3]
soma(*lista)  # 6

*args recebe argumentos extras como tuple. **kwargs recebe nomeados como dict. Ordem: parâmetros normais, *args, keyword-only, **kwargs. *lista faz unpack.

Type Hints
from typing import Optional, Union, List, Dict

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

def buscar(id: int) -> Optional[str]:
    ...  # retorna str ou None

def processar(x: int | str) -> None:
    ...  # Python 3.10+: union com |

def total(nums: list[int]) -> float:
    return sum(nums)

def config() -> dict[str, int]:
    return {"timeout": 30}

# Callable
from typing import Callable
def aplicar(fn: Callable[[int], str], x: int) -> str:
    return fn(x)

Type hints não são forçados em runtime — são para IDEs e mypy. Optional[X] = X | None. Python 3.10+: int | str em vez de Union. list[int] em vez de List[int] (3.9+).

functools — Ferramentas
from functools import lru_cache, partial, reduce, wraps

# lru_cache: memoization
@lru_cache(maxsize=128)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

# cache (3.9+): lru_cache sem limite
from functools import cache
@cache
def fatorial(n):
    return n * fatorial(n-1) if n else 1

# partial: fixa argumentos
def potencia(base, exp):
    return base ** exp
quadrado = partial(potencia, exp=2)
quadrado(5)  # 25

# singledispatch: overload por tipo
from functools import singledispatch
@singledispatch
def processar(x):
    return str(x)
@processar.register(list)
def _(x):
    return ", ".join(map(str, x))

@lru_cache memoiza resultados. @cache (3.9+) é sem limite. partial() fixa argumentos de uma função. @singledispatch cria funções genéricas com overload por tipo. @wraps preserva metadados em decorators.

Lambda e Funções Anónimas
# Lambda: função de uma expressão
dobro = lambda x: x * 2
dobro(5)  # 10

soma = lambda a, b: a + b
soma(3, 4)  # 7

# Uso comum: ordenação
pessoas = [("Ana", 30), ("Rui", 25), ("Eva", 35)]
pessoas.sort(key=lambda p: p[1])
# [("Rui", 25), ("Ana", 30), ("Eva", 35)]

# Com map/filter
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x**2, nums))     # [1, 4, 9, 16, 25]
list(filter(lambda x: x > 3, nums)) # [4, 5]

# sorted com key
sorted(pessoas, key=lambda p: p[0])

lambda cria funções anónimas de uma expressão. Ideal para key em sort()/sorted() e callbacks curtos. Para lógica complexa, use def normal.

Funções Embutidas Úteis
# Transformação
list(map(str, [1, 2, 3]))       # ['1', '2', '3']
list(filter(None, [0, 1, "", "a"]))  # [1, "a"]

# Agregação
sum([1, 2, 3])       # 6
min([3, 1, 2])       # 1
max([3, 1, 2])       # 3
len([1, 2, 3])       # 3

# Inspeção
type(42)             # <class 'int'>
isinstance(42, int)  # True
dir(str)             # métodos de str
help(print)          # documentação

# Outras
sorted([3, 1, 2])          # [1, 2, 3]
reversed([1, 2, 3])        # iterator
abs(-5)                    # 5
divmod(17, 5)              # (3, 2)

Funções embutidas: map(), filter(), sum(), min(), max(), len(), sorted(), abs(), divmod(). isinstance() verifica tipo. dir() lista atributos.

Decorators
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        fim = time.perf_counter()
        print(f"{func.__name__}: {fim-inicio:.4f}s")
        return resultado
    return wrapper

@timer
def processo_lento():
    time.sleep(1)

processo_lento()  # "processo_lento: 1.0012s"

# Decorator com argumentos
def repetir(n):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

Decorators envolvem funções com @nome. O wrapper adiciona comportamento antes/depois. @functools.wraps preserva metadados. Decorators com argumentos precisam de 3 níveis. Usados em logging, cache, auth.

Recursão
# Fatorial
def fatorial(n: int) -> int:
    if n <= 1:
        return 1
    return n * fatorial(n - 1)

fatorial(5)  # 120

# Fibonacci
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

# Com memoization
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_cache(n: int) -> int:
    if n < 2:
        return n
    return fib_cache(n - 1) + fib_cache(n - 2)

fib_cache(100)  # instantâneo

Recursão: função que se chama a si própria. Precisa de caso base para parar. @lru_cache memoiza resultados (evita recálculos). Python tem limite de recursão (~1000) — para casos grandes, prefira iteração.

Classes e OOP


10 cards
Classe Básica
class Pessoa:
    # Atributo de classe
    especie = "Homo sapiens"

    def __init__(self, nome: str, idade: int):
        # Atributos de instância
        self.nome = nome
        self.idade = idade

    def apresentar(self) -> str:
        return f"Sou {self.nome}, {self.idade} anos"

    def __repr__(self):
        return f"Pessoa({self.nome!r}, {self.idade})"

ana = Pessoa("Ana", 30)
ana.apresentar()  # "Sou Ana, 30 anos"
ana.nome          # "Ana"

__init__ é o construtor. self é a instância (obrigatório). Atributos de classe são partilhados; de instância são únicos. __repr__ define a representação. Criação: Pessoa("Ana", 30).

Dunder Methods
class Vetor:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vetor(self.x + other.x, self.y + other.y)

    def __repr__(self):
        return f"Vetor({self.x}, {self.y})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __len__(self):
        return 2

    def __getitem__(self, i):
        return (self.x, self.y)[i]

v = Vetor(1, 2) + Vetor(3, 4)
print(v)      # Vetor(4, 6)
v[0]          # 4

Dunder methods (__x__) definem comportamento de operadores. __add__ para +, __eq__ para ==, __repr__ para print, __len__ para len(), __getitem__ para indexação.

__slots__ e Performance
class Ponto:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Ponto(1, 2)
p.x = 10      # OK
p.z = 3       # AttributeError!

# Sem __slots__: cada instância tem __dict__
# Com __slots__: sem __dict__, menos memória

# Comparação
import sys
class Normal:
    def __init__(self): self.x = 1

class Slim:
    __slots__ = ('x',)
    def __init__(self): self.x = 1

sys.getsizeof(Normal().__dict__)  # ~104 bytes
# Slim: sem __dict__

__slots__ impede criação de atributos dinâmicos e elimina o __dict__ por instância. Reduz memória em ~40% para muitas instâncias. Não pode adicionar atributos não declarados. Ideal para classes com milhões de instâncias.

Herança e Polimorfismo
class Animal:
    def __init__(self, nome: str):
        self.nome = nome

    def falar(self) -> str:
        raise NotImplementedError

class Cao(Animal):
    def falar(self) -> str:
        return f"{self.nome}: Au au!"

class Gato(Animal):
    def falar(self) -> str:
        return f"{self.nome}: Miau!"

# Polimorfismo
animais = [Cao("Rex"), Gato("Felix")]
for a in animais:
    print(a.falar())

# Herança múltipla
class AnimalDomestico(Animal, Mascote):
    pass

Herança com class Filha(Pai). super() chama métodos do pai. Polimorfismo: mesmo método, comportamento diferente. Python suporta herança múltipla com MRO (Method Resolution Order).

Dataclass (3.7+)
from dataclasses import dataclass, field

@dataclass
class Produto:
    nome: str
    preco: float
    quantidade: int = 0
    tags: list[str] = field(default_factory=list)

    @property
    def total(self) -> float:
        return self.preco * self.quantidade

p1 = Produto("Livro", 19.99, 2)
p2 = Produto("Livro", 19.99, 2)

p1 == p2       # True (compara campos!)
print(p1)      # Produto(nome='Livro', preco=19.99, ...)
p1.total       # 39.98

# Imutável
@dataclass(frozen=True)
class Ponto:
    x: float
    y: float

@dataclass gera __init__, __repr__, __eq__ automaticamente. field(default_factory=list) para mutáveis. frozen=True torna imutável. Ideal para DTOs e value objects.

Enums (3.4+)
from enum import Enum, IntEnum, auto, Flag

class Cor(Enum):
    VERMELHO = 1
    VERDE = 2
    AZUL = 3

class Status(Enum):
    ATIVO = auto()    # 1
    INATIVO = auto()  # 2

# Backed enum (com valores)
class HttpMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

# IntEnum: comparável com int
class Prioridade(IntEnum):
    BAIXA = 1
    MEDIA = 2
    ALTA = 3

Prioridade.ALTA > Prioridade.BAIXA  # True

# Flag: bitwise
class Perm(Flag):
    READ = auto()
    WRITE = auto()
    EXEC = auto()

Enum cria conjuntos de constantes nomeadas. auto() atribui valores automáticos. IntEnum é comparável com inteiros. Flag suporta operações bitwise (|, &). Aceda com .value e .name.

super() e Herança
class Veiculo:
    def __init__(self, marca: str, ano: int):
        self.marca = marca
        self.ano = ano

class Carro(Veiculo):
    def __init__(self, marca: str, ano: int, portas: int):
        super().__init__(marca, ano)
        self.portas = portas

    def info(self) -> str:
        base = f"{self.marca} ({self.ano})"
        return f"{base} - {self.portas} portas"

# MRO
print(Carro.__mro__)
# (Carro, Veiculo, object)

super().__init__() chama o construtor da classe pai. Essencial em herança para inicializar atributos do pai. __mro__ mostra a ordem de resolução de métodos. Em herança múltipla, siga a ordem do MRO.

Métodos de Classe e Estáticos
class Data:
    def __init__(self, dia, mes, ano):
        self.dia = dia
        self.mes = mes
        self.ano = ano

    @classmethod
    def from_string(cls, s: str) -> "Data":
        dia, mes, ano = map(int, s.split("/"))
        return cls(dia, mes, ano)

    @staticmethod
    def e_valida(s: str) -> bool:
        try:
            d, m, a = map(int, s.split("/"))
            return 1 <= m <= 12 and 1 <= d <= 31
        except ValueError:
            return False

# Factory method
d = Data.from_string("25/12/2024")
Data.e_valida("31/02/2024")  # False

@classmethod recebe cls (a classe) — ideal para factory methods. @staticmethod não recebe self nem cls — é uma função normal na classe. Use @classmethod para construtores alternativos.

Properties
class Circulo:
    def __init__(self, raio: float):
        self._raio = raio

    @property
    def raio(self) -> float:
        return self._raio

    @raio.setter
    def raio(self, valor: float):
        if valor < 0:
            raise ValueError("Raio não pode ser negativo")
        self._raio = valor

    @property
    def area(self) -> float:
        return 3.14159 * self._raio ** 2

c = Circulo(5)
c.raio        # 5 (getter)
c.raio = 10   # setter com validação
c.area        # 314.159 (read-only)

@property cria getters/setters com sintaxe de atributo. @x.setter para validação na atribuição. Sem setter = read-only. Convenção: atributo interno com _ prefix. Acesso: obj.prop (sem parênteses).

ABC e Protocol
from abc import ABC, abstractmethod

class Forma(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimetro(self) -> float: ...

class Circulo(Forma):
    def __init__(self, raio):
        self.raio = raio

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

    def perimetro(self):
        return 2 * 3.14159 * self.raio

# Protocol (duck typing estático)
from typing import Protocol

class Renderizavel(Protocol):
    def render(self) -> str: ...

ABC + @abstractmethod força implementação nas subclasses. Não pode instanciar ABC diretamente. Protocol (3.8+) define interfaces por duck typing estático — sem herança explícita. Verificado pelo mypy.

Coleções


9 cards
Listas
frutas = ["maçã", "banana", "cereja"]

# Acesso e modificação
frutas[0]       # "maçã"
frutas[-1]      # "cereja" (último)
frutas[1] = "kiwi"

# Métodos
frutas.append("uva")       # adiciona no fim
frutas.insert(1, "manga")  # insere no índice
frutas.remove("banana")    # remove por valor
frutas.pop()               # remove e retorna último
frutas.extend(["a", "b"])  # estende

# Verificação
"kiwi" in frutas    # True
len(frutas)         # comprimento
frutas.index("kiwi")  # índice

# Cópia
copia = frutas.copy()
copia = frutas[:]

Listas são mutáveis e ordenadas. Índices negativos contam do fim. append() adiciona, pop() remove. in verifica existência. Copie com .copy() ou [:] (cópia rasa).

Slicing (Fatiamento)
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

nums[2:5]     # [2, 3, 4]
nums[:3]      # [0, 1, 2]
nums[7:]      # [7, 8, 9]
nums[-3:]     # [7, 8, 9]
nums[::2]     # [0, 2, 4, 6, 8] (passo 2)
nums[::-1]    # [9, 8, ..., 0] (inverter)
nums[1:8:2]   # [1, 3, 5, 7]

# Strings
"Python"[1:4]   # "yth"
"Python"[::-1]  # "nohtyP"

# Atribuição com slice
nums[2:5] = [20, 30, 40]

# Cópia rasa
copia = nums[:]

Slicing: [início:fim:passo]. fim é exclusivo. [::-1] inverte. [::2] salta de 2 em 2. Funciona em listas, strings e tuples. Atribuição com slice modifica a lista original.

Arrays e Memory
import array
import sys

# array: tipo fixo, menos memória
nums = array.array('i', [1, 2, 3, 4, 5])
nums.append(6)
nums[0]  # 1

# Tipos: 'i' int, 'f' float, 'd' double, 'b' byte
print(sys.getsizeof([1,2,3]))       # ~88 bytes (list)
print(sys.getsizeof(array.array('i', [1,2,3])))  # ~76 bytes

# bytes e bytearray
dados = b"\x48\x65\x6c\x6c\x6f"  # b"Hello"
mutavel = bytearray(b"abc")
mutavel[0] = 65  # b"Abc"

# memoryview (zero-copy)
view = memoryview(bytearray(1024))
view[0:4] = b"test"

# Para números: numpy
# import numpy as np
# arr = np.array([1, 2, 3])

array.array é lista com tipo fixo — menos memória. bytes é imutável, bytearray é mutável. memoryview permite acesso zero-copy a buffers. Para computação numérica, use numpy.

Dicionários
pessoa = {"nome": "Ana", "idade": 30}

# Acesso
pessoa["nome"]           # "Ana"
pessoa.get("email")      # None (sem erro)
pessoa.get("email", "N/A")  # "N/A"

# Modificação
pessoa["email"] = "ana@mail.com"
del pessoa["idade"]
pessoa.update({"cidade": "Lisboa"})

# Iteração
for chave in pessoa:
    print(chave, pessoa[chave])
for chave, valor in pessoa.items():
    print(f"{chave}: {valor}")

# Verificação
"nome" in pessoa    # True
len(pessoa)         # 2

# Dict comprehension
quadrados = {x: x**2 for x in range(5)}

Dicts mapeiam chave → valor. .get() evita KeyError. .items() itera pares. in verifica chaves. Desde Python 3.7, dicts mantêm ordem de inserção. Chaves devem ser hashable.

Ordenação
nums = [3, 1, 4, 1, 5, 9]

# sorted: retorna nova lista
sorted(nums)              # [1, 1, 3, 4, 5, 9]
sorted(nums, reverse=True)  # [9, 5, 4, 3, 1, 1]

# .sort(): modifica in-place
nums.sort()

# Com key
palavras = ["banana", "figo", "abacaxi"]
sorted(palavras, key=len)  # por comprimento
sorted(palavras, key=str.lower)

# Lista de tuples
pessoas = [("Ana", 30), ("Rui", 25)]
sorted(pessoas, key=lambda p: p[1])

# operator.itemgetter
from operator import itemgetter
sorted(pessoas, key=itemgetter(1))

sorted() retorna nova lista; .sort() modifica in-place. key define critério. reverse=True para descendente. operator.itemgetter é mais rápido que lambda para tuples.

Tuples e NamedTuple
# Tuple: imutável
ponto = (3, 4)
x, y = ponto  # unpacking

# Sem parênteses também funciona
coords = 10, 20, 30

# Tuple de 1 elemento
single = (42,)  # vírgula obrigatória!

# NamedTuple (tuple com nomes)
from collections import namedtuple
Ponto = namedtuple("Ponto", ["x", "y"])
p = Ponto(3, 4)
p.x  # 3
p.y  # 4

# typing.NamedTuple (com type hints)
from typing import NamedTuple
class Ponto2(NamedTuple):
    x: float
    y: float

p2 = Ponto2(1.5, 2.5)

Tuples são imutáveis — mais rápidos que listas. Unpacking: x, y = ponto. Tuple de 1 elemento precisa de vírgula: (42,). namedtuple adiciona nomes aos campos. Ideal para retornar múltiplos valores.

collections
from collections import Counter, defaultdict, deque

# Counter: conta ocorrências
texto = "abracadabra"
c = Counter(texto)
c.most_common(2)  # [('a', 5), ('b', 2)]

# defaultdict: valor por omissão
grupos = defaultdict(list)
for nome, grupo in [("Ana", "A"), ("Rui", "B"), ("Eva", "A")]:
    grupos[grupo].append(nome)
# {'A': ['Ana', 'Eva'], 'B': ['Rui']}

# deque: fila com operações O(1) nas pontas
fila = deque([1, 2, 3])
fila.appendleft(0)  # [0, 1, 2, 3]
fila.popleft()      # 0 (O(1)!)
fila.rotate(1)      # roda elementos

Counter conta elementos e dá most_common(). defaultdict cria valor automaticamente para chaves novas. deque é fila com appendleft()/popleft() em O(1) — mais rápido que lista para filas.

Sets
# Set: coleção sem duplicados
nums = {1, 2, 3, 3, 2}  # {1, 2, 3}

# Operações
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b   # união: {1, 2, 3, 4, 5, 6}
a & b   # interseção: {3, 4}
a - b   # diferença: {1, 2}
a ^ b   # simétrica: {1, 2, 5, 6}

# Métodos
a.add(10)
a.discard(1)    # remove sem erro
a.issubset(b)   # False
a.issuperset(b) # False

# Remover duplicados de lista
lista = [1, 2, 2, 3, 3, 3]
unica = list(set(lista))  # [1, 2, 3]

# Set vazio
s = set()  # {} cria dict vazio!

Sets são coleções sem duplicados e não ordenadas. Operações: | união, & interseção, - diferença, ^ simétrica. {} cria dict vazio — use set(). Ideal para membership testing (O(1)).

Empacotamento e Unpacking
# Unpacking básico
a, b, c = [1, 2, 3]

# Com * (resto)
primeiro, *meio, ultimo = [1, 2, 3, 4, 5]
# primeiro=1, meio=[2,3,4], ultimo=5

# Ignorar valores
_, segundo, _ = (10, 20, 30)

# Merge de dicts (3.9+)
d1 = {"a": 1}
d2 = {"b": 2}
merged = d1 | d2  # {"a": 1, "b": 2}

# Unpack em função
def soma(a, b, c):
    return a + b + c
soma(*[1, 2, 3])  # 6

# Unpack dict
def perfil(nome, idade):
    pass
perfil(**{"nome": "Ana", "idade": 30})

Unpacking: a, b, c = lista. *resto captura elementos restantes. _ ignora valores. d1 | d2 faz merge de dicts (3.9+). *args e **kwargs fazem unpack em chamadas de função.

Compreensões e Iteradores


7 cards
List Comprehension
# Básica
quadrados = [x**2 for x in range(10)]

# Com filtro
pares = [x for x in range(20) if x % 2 == 0]

# Com transformação
nomes = ["ana", "rui", "eva"]
maiusculos = [n.upper() for n in nomes]

# Aninhada (achatar matriz)
matriz = [[1, 2], [3, 4], [5, 6]]
plano = [x for sub in matriz for x in sub]
# [1, 2, 3, 4, 5, 6]

# Com condição inline
resultado = [x if x > 0 else 0 for x in nums]

# vs map/filter
[x**2 for x in nums if x > 3]
# equivale a: list(map(lambda x: x**2, filter(lambda x: x > 3, nums)))

Sintaxe: [expressão for item in iterável if condição]. Mais legível que map()/filter(). Para aninhadas: for externo primeiro. Evite comprehensions muito complexas — use loop normal.

itertools
from itertools import (
    chain, product, combinations,
    permutations, groupby, islice, cycle
)

# chain: concatena iteráveis
list(chain([1, 2], [3, 4]))  # [1, 2, 3, 4]

# product: produto cartesiano
list(product("AB", "12"))
# [('A','1'), ('A','2'), ('B','1'), ('B','2')]

# combinations e permutations
list(combinations([1,2,3], 2))  # [(1,2),(1,3),(2,3)]
list(permutations([1,2,3], 2))  # 6 pares

# groupby (requer ordenação)
dados = sorted(pessoas, key=lambda p: p[1])
for grupo, itens in groupby(dados, key=lambda p: p[1]):
    print(grupo, list(itens))

# islice: slice de iterador
list(islice(range(100), 5, 10))

itertools tem ferramentas para iteráveis. chain() concatena. product() faz produto cartesiano. combinations()/permutations() para combinatória. groupby() agrupa (requer dados ordenados). Tudo é lazy.

Dict e Set Comprehension
# Dict comprehension
quadrados = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Inverter dict
original = {"a": 1, "b": 2, "c": 3}
invertido = {v: k for k, v in original.items()}

# Filtrar dict
notas = {"Ana": 15, "Rui": 8, "Eva": 12}
aprovados = {k: v for k, v in notas.items() if v >= 10}

# Set comprehension
unicos = {x % 5 for x in range(20)}
# {0, 1, 2, 3, 4}

# De lista de tuples
pares = [(1, "um"), (2, "dois")]
d = {k: v for k, v in pares}

Dict comprehension: {chave: valor for ...}. Set comprehension: {expressão for ...}. Ideal para transformar/inverter dicts e criar sets com lógica. Mesma sintaxe que list comp mas com {}.

reduce e functools
from functools import reduce, lru_cache, partial

# reduce: acumula
nums = [1, 2, 3, 4, 5]
reduce(lambda acc, x: acc + x, nums)  # 15
reduce(lambda acc, x: acc * x, nums)  # 120

# Com valor inicial
reduce(lambda acc, x: acc + x, nums, 100)  # 115

# lru_cache: memoization
@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

# partial: fixa argumentos
from functools import partial
dobro = partial(int, base=2)
dobro("1010")  # 10

# accumulate
from itertools import accumulate
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]

reduce() acumula valores (soma, produto, etc.). @lru_cache memoiza resultados de funções puras. partial() fixa argumentos de uma função. accumulate() é como reduce mas retorna todos os passos.

Generators
# Generator function
def contagem(n):
    i = 0
    while i < n:
        yield i
        i += 1

for x in contagem(5):
    print(x)  # 0, 1, 2, 3, 4

# Generator expression (lazy)
soma = sum(x**2 for x in range(1000000))
# Não cria lista em memória!

# vs List comprehension
import sys
lista = [x for x in range(10000)]
gen = (x for x in range(10000))
sys.getsizeof(lista)  # ~87616 bytes
sys.getsizeof(gen)    # ~112 bytes

# yield from (delegar)
def chain(*iterables):
    for it in iterables:
        yield from it

yield cria generators: avaliação lazy (um valor de cada vez). (x for x in ...) é generator expression. Usa muito menos memória que listas. yield from delega para outro iterável. Ideal para sequências infinitas.

Iteradores Personalizados
# Protocolo de iteração: __iter__ + __next__
class ContagemRegressiva:
    def __init__(self, inicio):
        self.n = inicio

    def __iter__(self):
        return self

    def __next__(self):
        if self.n <= 0:
            raise StopIteration
        self.n -= 1
        return self.n + 1

for x in ContagemRegressiva(5):
    print(x)  # 5, 4, 3, 2, 1

# Mais simples: generator
def contagem(n):
    while n > 0:
        yield n
        n -= 1

# iter() com callable
import random
rolar = iter(lambda: random.randint(1, 6), 6)
# Gera números até sair 6

Iteradores implementam __iter__() e __next__(). StopIteration termina a iteração. Generators com yield são mais simples. iter(callable, sentinel) chama até retornar o sentinel.

zip, enumerate e map
# zip: combina iteráveis
nomes = ["Ana", "Rui"]
idades = [30, 25]
for nome, idade in zip(nomes, idades):
    print(f"{nome}: {idade}")

# zip para criar dict
d = dict(zip(nomes, idades))

# enumerate: índice + valor
for i, fruta in enumerate(["maçã", "banana"]):
    print(f"{i}: {fruta}")

# map: aplica função a todos
list(map(str.upper, ["olá", "mundo"]))
list(map(lambda x: x*2, [1, 2, 3]))

# filter: mantém os True
list(filter(lambda x: x > 2, [1, 2, 3, 4]))

# zip_longest (tamanhos diferentes)
from itertools import zip_longest
list(zip_longest([1, 2], [3], fillvalue=0))

zip() combina iteráveis em pares. enumerate() dá índice + valor. map() aplica função a todos. filter() mantém elementos truthy. zip_longest() para tamanhos diferentes com fillvalue.

Exceções e Ficheiros


9 cards
try / except / finally
try:
    resultado = 10 / 0
except ZeroDivisionError as e:
    print(f"Erro: {e}")
except (TypeError, ValueError) as e:
    print(f"Tipo ou valor: {e}")
except Exception as e:
    print(f"Genérico: {e}")
else:
    print("Sem erros!")
finally:
    print("Sempre executa")

# Múltiplas exceções
try:
    valor = int(input())
except ValueError:
    print("Não é número")
except KeyboardInterrupt:
    print("Cancelado")

try/except captura erros. as e dá acesso à exceção. else executa se não houver erro. finally executa sempre (cleanup). Capture exceções específicas antes das genéricas.

JSON e CSV
import json
import csv

# JSON
dados = {"nome": "Ana", "idade": 30}
json_str = json.dumps(dados, ensure_ascii=False, indent=2)
objeto = json.loads(json_str)

# JSON em ficheiro
with open("dados.json", "w") as f:
    json.dump(dados, f, ensure_ascii=False)
with open("dados.json") as f:
    dados = json.load(f)

# CSV
with open("dados.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["Nome", "Idade"])
    writer.writerow(["Ana", 30])

with open("dados.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["Nome"])

json.dumps()/loads() para strings. json.dump()/load() para ficheiros. ensure_ascii=False para acentos. csv.DictReader lê com nomes de coluna. csv.writer escreve linhas.

ExceptionGroup (3.11+)
# ExceptionGroup: múltiplas exceções (3.11+)
try:
    raise ExceptionGroup("erros", [
        ValueError("valor inválido"),
        TypeError("tipo errado"),
    ])
except* ValueError as eg:
    for e in eg.exceptions:
        print(f"ValueError: {e}")
except* TypeError as eg:
    for e in eg.exceptions:
        print(f"TypeError: {e}")

# TaskGroup (asyncio, 3.11+)
import asyncio

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(fetch("url1"))
        t2 = tg.create_task(fetch("url2"))
    # Se uma falhar, todas são canceladas
    print(t1.result(), t2.result())

ExceptionGroup (3.11+) agrupa múltiplas exceções. except* captura por tipo dentro do grupo. asyncio.TaskGroup (3.11+) executa tasks concurrently — se uma falhar, cancela todas. Substitui gather() com melhor tratamento de erros.

Lançar Exceções
# raise
def dividir(a, b):
    if b == 0:
        raise ValueError("Divisor não pode ser zero")
    return a / b

# Re-lançar
try:
    processar()
except Exception as e:
    log(e)
    raise  # re-lança a mesma exceção

# Exceção personalizada
class SaldoInsuficiente(Exception):
    def __init__(self, saldo, valor):
        self.saldo = saldo
        self.valor = valor
        super().__init__(
            f"Saldo {saldo} < {valor}"
        )

raise SaldoInsuficiente(50, 100)

raise lança exceções. Crie classes com extends Exception para erros específicos. raise sem argumentos re-lança a exceção atual. Inclua mensagens claras e dados relevantes.

pathlib
from pathlib import Path

# Criar paths
p = Path("/var/www/dados")
p = Path.home() / "Documentos" / "file.txt"

# Informações
p.name        # "file.txt"
p.stem        # "file"
p.suffix      # ".txt"
p.parent      # Path("Documentos")
p.exists()    # True/False
p.is_file()   # True/False
p.is_dir()    # True/False

# Operações
p.mkdir(parents=True, exist_ok=True)
p.touch()
p.rename("novo.txt")
p.unlink()    # apagar ficheiro

# Listar
for f in Path(".").glob("*.py"):
    print(f)
for f in Path(".").rglob("*.txt"):
    print(f)  # recursivo

pathlib.Path é a forma moderna de trabalhar com paths. / concatena caminhos. .glob() encontra ficheiros. .rglob() é recursivo. .read_text()/.write_text() para I/O rápido. Substitui os.path.

Context Managers (with)
# with: garante cleanup
with open("ficheiro.txt") as f:
    conteudo = f.read()
# Ficheiro fechado automaticamente!

# Múltiplos recursos
with open("a.txt") as a, open("b.txt") as b:
    dados = a.read() + b.read()

# Context manager personalizado
class Timer:
    def __enter__(self):
        import time
        self.inicio = time.perf_counter()
        return self

    def __exit__(self, *args):
        import time
        self.fim = time.perf_counter()
        print(f"Tempo: {self.fim - self.inicio:.4f}s")

with Timer():
    processo_lento()

# contextlib
from contextlib import contextmanager

with garante cleanup mesmo com exceções. __enter__/__exit__ definem o protocolo. Ideal para ficheiros, conexões BD, locks. @contextmanager cria context managers com yield.

Logging
import logging

# Configuração básica
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("app.log"),
        logging.StreamHandler(),
    ]
)

logger = logging.getLogger(__name__)

logger.debug("Detalhe técnico")
logger.info("Operação normal")
logger.warning("Atenção!")
logger.error("Algo falhou")
logger.critical("Erro grave!")

# Com exceção
try:
    1 / 0
except Exception:
    logger.exception("Erro na divisão")

logging substitui print() para debug. Níveis: DEBUG, INFO, WARNING, ERROR, CRITICAL. getLogger(__name__) por módulo. logger.exception() inclui traceback automaticamente.

Operações com Ficheiros
# Ler
with open("dados.txt", "r", encoding="utf-8") as f:
    conteudo = f.read()       # tudo
    linhas = f.readlines()    # lista de linhas

# Ler linha a linha (memória eficiente)
with open("grande.log") as f:
    for linha in f:
        processar(linha)

# Escrever
with open("output.txt", "w") as f:
    f.write("Olá\n")
    f.writelines(["a\n", "b\n"])

# Append
with open("log.txt", "a") as f:
    f.write("nova linha\n")

# pathlib (moderno)
from pathlib import Path
p = Path("dados.txt")
p.read_text(encoding="utf-8")
p.write_text("conteúdo")
p.exists()

open() com with fecha automaticamente. Modos: r ler, w escrever, a append. encoding="utf-8" para acentos. pathlib.Path é a forma moderna e OOP.

assert e Debugging
# assert: verifica condição em desenvolvimento
def dividir(a, b):
    assert b != 0, "Divisor não pode ser zero"
    return a / b

# assert com mensagem
assert isinstance(nome, str), f"Esperado str, got {type(nome)}"

# breakpoint() (Python 3.7+)
def processo(x):
    resultado = x * 2
    breakpoint()  # abre debugger (pdb)
    return resultado + 1

# No pdb:
# n = next, s = step, c = continue
# p variavel = print
# l = listar código

# python -m pdb script.py
# PYTHONBREAKPOINT=0 desativa

assert verifica condições em desenvolvimento (removido com -O). breakpoint() abre o debugger pdb. Comandos: n next, s step, c continue, p print. Não use assert para validação de input.

Módulos e Pacotes


6 cards
Importar Módulos
# Formas de importar
import os
import os.path
from os import path
from os.path import join, exists
import numpy as np
from typing import List, Optional

# Import condicional
try:
    import ujson as json
except ImportError:
    import json

# Import relativo (dentro de pacote)
from . import utils
from ..models import User

# __all__ controla from x import *
# __init__.py:
__all__ = ["ClasseA", "funcao_b"]

import módulo importa tudo. from x import y importa específico. as cria alias. Import relativo com . e ... __all__ controla exports. Evite from x import *.

Criar um Pacote
# Estrutura:
# meu_pacote/
#   __init__.py
#   core.py
#   utils/
#     __init__.py
#     helpers.py

# __init__.py (exporta API pública)
from .core import ClassePrincipal
from .utils.helpers import funcao_util

__all__ = ["ClassePrincipal", "funcao_util"]
__version__ = "1.0.0"

# Uso:
from meu_pacote import ClassePrincipal
from meu_pacote.utils import helpers

# __name__ == "__main__"
if __name__ == "__main__":
    # Só executa como script
    print("Executado diretamente")

Pacotes precisam de __init__.py. __all__ define exports públicos. __name__ == "__main__" verifica se é script principal. Estrutura: pasta com __init__.py + módulos. __version__ por convenção.

pip e venv
# Ambiente virtual
python -m venv .venv
source .venv/bin/activate   # Linux/Mac
.venv\Scripts\activate      # Windows
deactivate

# pip
pip install requests
pip install "flask>=2.0,<3.0"
pip install -r requirements.txt
pip freeze > requirements.txt
pip uninstall requests
pip list
pip show requests

# pyproject.toml (moderno)
# [project]
# dependencies = [
#     "requests>=2.28",
#     "flask>=2.0",
# ]

# pip-tools / poetry / uv
pip install pip-tools
pip-compile pyproject.toml

venv isola dependências por projeto. pip install instala pacotes. requirements.txt lista dependências. pyproject.toml é o formato moderno. Ferramentas: poetry, uv, pip-tools.

__name__ e Scripts
# script.py
def main():
    print("Programa principal")

if __name__ == "__main__":
    main()

# Quando executado: __name__ == "__main__"
# Quando importado: __name__ == "script"

# CLI com argparse
import argparse

parser = argparse.ArgumentParser(description="Tool")
parser.add_argument("nome", help="Nome")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-n", "--count", type=int, default=1)
args = parser.parse_args()

if args.verbose:
    print(f"Olá {args.nome}!" * args.count)

if __name__ == "__main__" separa código de execução vs importação. argparse cria CLI com flags e ajuda automática. action="store_true" para flags booleanas. type=int converte argumentos.

Módulos Padrão Úteis
import os, sys, json, math, random
import datetime, collections, itertools
import re, pathlib, subprocess, hashlib

# os / pathlib
os.getcwd()
os.environ.get("HOME")
Path.home()

# sys
sys.argv        # argumentos CLI
sys.version     # versão Python
sys.exit(1)     # sair com código

# math
math.pi         # 3.14159...
math.sqrt(16)   # 4.0
math.ceil(3.2)  # 4

# random
random.randint(1, 100)
random.choice([1, 2, 3])
random.shuffle(lista)

# hashlib
hashlib.sha256(b"texto").hexdigest()

A stdlib do Python é enorme. os/pathlib para ficheiros. sys para sistema. math para matemática. random para aleatoriedade. json, re, datetime, collections, itertools.

datetime
from datetime import datetime, date, timedelta

# Agora
agora = datetime.now()
hoje = date.today()

# Formatar
agora.strftime("%d/%m/%Y %H:%M")
# "25/12/2024 14:30"

# Parse
dt = datetime.strptime("25/12/2024", "%d/%m/%Y")

# Aritmética
amanha = hoje + timedelta(days=1)
daqui_1h = agora + timedelta(hours=1)
diferenca = dt2 - dt1
diferenca.days  # dias

# ISO
datetime.now().isoformat()
datetime.fromisoformat("2024-12-25T14:30:00")

# Comparação
if agora > dt:
    print("Passado")

datetime.now() dá data/hora atual. strftime() formata, strptime() faz parse. timedelta para aritmética. isoformat()/fromisoformat() para formato ISO. Subtração dá timedelta.

Dicas e Boas Práticas


8 cards
Walrus Operator (:=)
# Atribuição em expressão (Python 3.8+)
if (n := len(dados)) > 10:
    print(f"Lista grande: {n}")

# Em while loops
while (linha := input("> ")) != "sair":
    processar(linha)

# Em comprehensions
resultados = [
    y for x in dados
    if (y := processar(x)) is not None
]

# Com match (3.10+)
match dados:
    case {"nome": nome} if (n := len(nome)) > 3:
        print(f"Nome longo: {nome} ({n})")

# Sem walrus (antes):
# n = len(dados)
# if n > 10: ...

:= (walrus) atribui e retorna valor numa expressão. Evita linhas extra de atribuição. Útil em while, if e comprehensions. Não abuse — use só quando melhora legibilidade.

Anti-patterns a Evitar
# 1. Mutável como default (BUG!)
def mau(lista=[]):      # NUNCA!
    lista.append(1)
    return lista

def bom(lista=None):
    if lista is None:
        lista = []
    lista.append(1)
    return lista

# 2. Comparar com None usando ==
if x == None:    # mau
if x is None:    # bom

# 3. Loop com índice
for i in range(len(lista)):  # mau
    print(lista[i])
for item in lista:           # bom
    print(item)

# 4. Import *
from modulo import *  # mau (polui namespace)

# 5. except genérico
except:  # mau (captura tudo, até KeyboardInterrupt)
except Exception:  # melhor

Anti-patterns: default mutável em funções (use None), == None (use is None), loop com índice (use for item), import *, except: genérico. Siga PEP 8 e use ruff.

PEP 8 e Estilo
# Nomes
minha_variavel = 1      # snake_case
MINHA_CONSTANTE = 100   # UPPER_SNAKE
class MinhaClasse:      # PascalCase
    pass
def minha_funcao():     # snake_case
    pass

# Formatação
x = 1 + 2              # espaços em operadores
lista = [1, 2, 3]      # espaço após vírgula
func(a, b, c=3)        # sem espaço em = de kwargs

# Imports (topo do ficheiro)
import os               # stdlib
import requests         # terceiros
from . import utils     # local

# Linhas máx: 79 (código) / 72 (docs)
# Ferramentas: black, ruff, flake8, isort

PEP 8 é o guia de estilo. Nomes: snake_case para variáveis/funções, PascalCase para classes, UPPER_SNAKE para constantes. Ferramentas: black formata, ruff linta, isort ordena imports.

CLI com argparse e click
# argparse (stdlib)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("ficheiro")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-o", "--output", default="out.txt")
parser.add_argument("-n", type=int, default=10)
args = parser.parse_args()

# click (terceiro, mais elegante)
import click

@click.command()
@click.argument("ficheiro")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-n", "--count", default=10, type=int)
def processar(ficheiro, verbose, count):
    """Processa um ficheiro."""
    click.echo(f"Processando {ficheiro} x{count}")

if __name__ == "__main__":
    processar()

argparse (stdlib) para CLIs simples. click (terceiro) é mais elegante com decorators. action="store_true" para flags. type=int converte. Docstring vira --help. Para CLIs complexos: typer (type hints).

Truques Idiomáticos
# Comparações encadeadas
if 0 < x < 100:
    pass

# Ternário
status = "par" if n % 2 == 0 else "ímpar"

# Unpacking
primeiro, *resto = [1, 2, 3, 4, 5]

# dict.get com default
valor = d.get("chave", "default")

# any / all
if all(x > 0 for x in nums):
    print("Todos positivos")

# f-string debug (3.8+)
x = 42
print(f"{x = }")  # "x = 42"

# Inverter string
"inverter"[::-1]  # "retrevni"

# Merge dicts (3.9+)
merged = d1 | d2

# enumerate com start
for i, item in enumerate(lista, start=1):
    print(f"{i}. {item}")

Truques idiomáticos: comparações encadeadas, ternário, unpacking, dict.get(), any()/all(). f"{x = }" mostra nome e valor (debug). [::-1] inverte. | faz merge de dicts.

Dataclasses Avançadas
from dataclasses import dataclass, field, asdict, astuple

@dataclass(order=True)
class Produto:
    nome: str = field(compare=False)
    preco: float
    stock: int = 0
    tags: list[str] = field(default_factory=list)

    @property
    def disponivel(self) -> bool:
        return self.stock > 0

p = Produto("Livro", 19.99, 5)
asdict(p)    # {"nome": "Livro", "preco": 19.99, ...}
astuple(p)   # ("Livro", 19.99, 5, [])

# Ordenação por preco (order=True)
produtos = [Produto("A", 30), Produto("B", 10)]
sorted(produtos)  # ordena por preco

# slots=True (3.10+)
@dataclass(slots=True)
class Ponto:
    x: float
    y: float

@dataclass(order=True) gera métodos de comparação. field(compare=False) exclui campo da ordenação. asdict()/astuple() convertem. slots=True (3.10+) reduz memória. default_factory para mutáveis.

Performance e Otimização
# timeit: medir tempo
import timeit
timeit.timeit('"-".join(map(str, range(100)))', number=10000)

# cProfile: profiling
import cProfile
cProfile.run("minha_funcao()")

# Dicas de performance:
# 1. list comprehension > loop com append
# 2. dict/set lookup O(1) > list O(n)
# 3. join() > concatenação em loop
# 4. generators > listas para dados grandes
# 5. lru_cache para funções puras

# __slots__ para muitas instâncias
# local variables > global variables

# collections.deque > list para filas
# numpy para arrays numéricos

timeit mede tempo de execução. cProfile mostra profiling detalhado. Dicas: use comprehensions, dict/set para lookups, join() para strings, generators para dados grandes, @lru_cache para memoization.

Type Hints Avançados
from typing import TypeVar, Generic, Protocol, runtime_checkable

# TypeVar e Generic
T = TypeVar("T")

class Stack(Generic[T]):
    def __init__(self):
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

s: Stack[int] = Stack()
s.push(42)

# Protocol (structural subtyping)
@runtime_checkable
class Renderizavel(Protocol):
    def render(self) -> str: ...

def mostrar(obj: Renderizavel) -> None:
    print(obj.render())

# TypeAlias (3.12+)
# type Vector = list[float]

TypeVar + Generic criam classes genéricas. Protocol define interfaces por estrutura (duck typing estático). @runtime_checkable permite isinstance(). Python 3.12: type Alias = ... para type aliases. Verifique com mypy.

Strings e Regex


6 cards
Métodos Avançados de String
s = "olá mundo python"

s.capitalize()    # "Olá mundo python"
s.title()         # "Olá Mundo Python"
s.swapcase()      # "OLÁ MUNDO PYTHON"
s.center(30, "-") # "---olá mundo python---"
s.zfill(20)       # "0000olá mundo python"

# Verificação
s.isalpha()    # False (tem espaços)
s.isdigit()    # False
s.isalnum()    # False
s.isspace()    # False

# Join e split
"-".join(["a", "b", "c"])  # "a-b-c"
"a,b,c".split(",")         # ["a", "b", "c"]
"a  b  c".split()          # ["a", "b", "c"]

# partition
"nome@email.com".partition("@")
# ('nome', '@', 'email.com')

join() é mais eficiente que concatenação em loop. split() sem args divide por whitespace. partition() divide em 3 partes. zfill() preenche com zeros. is*() verifica tipo de caracteres.

Template e Textwrap
from string import Template
import textwrap

# Template (seguro para user input)
t = Template("Olá $nome, tens $idade anos")
t.substitute(nome="Ana", idade=30)
# safe_substitute não lança erro se faltar
t.safe_substitute(nome="Ana")

# textwrap
texto = "Texto muito longo que precisa de ser quebrado em várias linhas para ficar legível."
textwrap.fill(texto, width=40)
textwrap.dedent("""
    Remove indentação
    comum de todas as linhas
""")
textwrap.shorten(texto, width=30)  # "Texto muito longo que..."

# repr vs str
repr("olá\nmundo")  # "'olá\\nmundo'" (debug)
str("olá\nmundo")   # output formatado

Template é seguro para input do utilizador (sem injeção). textwrap.fill() quebra texto em linhas. textwrap.dedent() remove indentação comum. repr() mostra representação debug, str() mostra formatado.

Regex Básico
import re

texto = "Contacto: ana@mail.com ou 912-345-678"

# search: primeira ocorrência
m = re.search(r"\d{3}-\d{3}-\d{3}", texto)
if m:
    print(m.group())  # "912-345-678"

# findall: todas
emails = re.findall(r"[\w.]+@[\w.]+", texto)
# ["ana@mail.com"]

# sub: substituir
re.sub(r"\d", "X", "abc123")  # "abcXXX"

# match: início da string
re.match(r"\d+", "123abc")  # Match
re.match(r"\d+", "abc123")  # None

# compile (reutilizar)
padrao = re.compile(r"\b\w+@\w+\.\w+\b")
padrao.findall(texto)

re.search() encontra primeira ocorrência. re.findall() retorna todas. re.sub() substitui. re.match() só no início. re.compile() para reutilizar padrões. Use raw strings (r"...").

difflib e Comparação
import difflib

# Diferença entre textos
texto1 = "Olá Mundo\nPython 3".splitlines()
texto2 = "Olá Mundo\nPython 3.12".splitlines()

diff = difflib.unified_diff(texto1, texto2, lineterm="")
print("\n".join(diff))

# Similaridade
difflib.SequenceMatcher(None, "python", "pyton").ratio()
# 0.909...

# Sugestões (close matches)
difflib.get_close_matches("pytho", ["python", "java", "perl"])
# ["python"]

# ndiff: diferença caractere a caractere
list(difflib.ndiff("abc", "axc"))
# ['  a', '- b', '+ x', '  c']

difflib compara sequências. unified_diff() mostra diferenças em formato unified. SequenceMatcher.ratio() dá similaridade (0-1). get_close_matches() sugere correções. ndiff() mostra diferença caractere a caractere.

Regex Avançado
import re

# Grupos nomeados
m = re.search(
    r"(?P<nome>\w+)@(?P<dominio>[\w.]+)",
    "ana@mail.com"
)
m.group("nome")     # "ana"
m.group("dominio")  # "mail.com"

# Lookahead / Lookbehind
re.findall(r"\w+(?=@)", "ana@mail rui@test")
# ["ana", "rui"] (antes de @)

# Quantificadores
# \d{2,4}   2 a 4 dígitos
# \w+       1 ou mais
# \s*       0 ou mais espaços
# .+?       non-greedy

# Flags
re.findall(r"olá", texto, re.IGNORECASE)
re.split(r"\s+", texto, flags=re.MULTILINE)

# sub com função
re.sub(r"\d+", lambda m: str(int(m.group())*2), "a1b2")
# "a2b4"

Grupos nomeados: (?P...). Lookahead: (?=...). re.IGNORECASE ignora maiúsculas. sub() aceita função como substituição. non-greedy com ? após quantificador.

Encoding e Unicode
# Strings são Unicode por padrão
texto = "Olá 🌍"
len(texto)  # 7 (caracteres)

# Encode/Decode
bytes_data = texto.encode("utf-8")
texto2 = bytes_data.decode("utf-8")

# Ler ficheiro com encoding
with open("ficheiro.txt", encoding="utf-8") as f:
    conteudo = f.read()

# Detetar encoding (chardet)
# import chardet
# resultado = chardet.detect(bytes_data)

# Unicode escapes
"\u0041"      # "A"
"\U0001F600"  # "😀"
chr(65)       # "A"
ord("A")      # 65

# Normalização
import unicodedata
unicodedata.normalize("NFC", texto)

Python 3: strings são Unicode. encode() converte para bytes. decode() converte bytes para string. Sempre especifique encoding="utf-8" em ficheiros. chr()/ord() convertem caractere/código.

Async e Concorrência


6 cards
async / await
import asyncio

async def fetch_dados(url: str) -> str:
    print(f"Iniciar: {url}")
    await asyncio.sleep(1)  # simula I/O
    print(f"Done: {url}")
    return f"Dados de {url}"

async def main():
    # Sequencial (3s)
    r1 = await fetch_dados("api/1")
    r2 = await fetch_dados("api/2")

    # Concorrente (1s!)
    resultados = await asyncio.gather(
        fetch_dados("api/1"),
        fetch_dados("api/2"),
        fetch_dados("api/3"),
    )
    print(resultados)

asyncio.run(main())

async def define corrotina. await pausa até completar. asyncio.gather() executa em concurrently. asyncio.run() inicia o event loop. Ideal para I/O (HTTP, BD, ficheiros). Não use time.sleep() — use asyncio.sleep().

multiprocessing
from multiprocessing import Pool, cpu_count
import multiprocessing as mp

# Pool de processos
def quadrado(n):
    return n ** 2

if __name__ == "__main__":
    with Pool(processes=cpu_count()) as pool:
        resultados = pool.map(quadrado, range(10))
        # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

    # apply_async (não bloqueante)
    with Pool() as pool:
        r = pool.apply_async(quadrado, (10,))
        print(r.get(timeout=5))  # 100

    # starmap (múltiplos args)
    def potencia(base, exp):
        return base ** exp
    with Pool() as pool:
        pool.starmap(potencia, [(2,3), (3,2)])

print(f"CPUs: {cpu_count()}")

multiprocessing contorna o GIL com processos separados. Pool.map() distribui trabalho. cpu_count() dá número de cores. if __name__ == "__main__" obrigatório no Windows. Para I/O, prefira threading ou asyncio.

asyncio — Padrões
import asyncio

# Task: criar e cancelar
async def tarefa():
    await asyncio.sleep(10)

async def main():
    task = asyncio.create_task(tarefa())
    task.cancel()

# Semaphore: limitar concorrência
sem = asyncio.Semaphore(5)

async def fetch_limitado(url):
    async with sem:
        await asyncio.sleep(1)
        return url

# Queue: produtor/consumidor
async def produtor(queue):
    for i in range(10):
        await queue.put(i)
    await queue.put(None)

async def consumidor(queue):
    while (item := await queue.get()) is not None:
        processar(item)

create_task() agenda corrotina. Semaphore limita concorrência. asyncio.Queue para produtor/consumidor. task.cancel() cancela. Use async with para recursos assíncronos.

async for e async with
import asyncio

# async for: iterador assíncrono
async def ler_linhas(ficheiro):
    async with aiofiles.open(ficheiro) as f:
        async for linha in f:
            yield linha.strip()

async def main():
    async for linha in ler_linhas("dados.txt"):
        processar(linha)

# async with: context manager assíncrono
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

# async generator
async def ticker(intervalo, n):
    for i in range(n):
        await asyncio.sleep(intervalo)
        yield i

async for itera sobre iteradores assíncronos. async with para context managers assíncronos (conexões, ficheiros). async generators com yield em funções async. Use bibliotecas como aiohttp e aiofiles para I/O assíncrono.

Threading
from threading import Thread, Lock
import threading

# Thread básica
def worker(nome):
    print(f"Thread {nome} a correr")

t = Thread(target=worker, args=("A",))
t.start()
t.join()  # espera terminar

# Múltiplas threads
threads = []
for i in range(5):
    t = Thread(target=worker, args=(str(i),))
    threads.append(t)
    t.start()
for t in threads:
    t.join()

# Lock (thread-safe)
lock = Lock()
contador = 0

def incrementar():
    global contador
    with lock:
        contador += 1

# GIL: threads Python não paralelizam CPU
# Use para I/O, não para CPU-bound

Thread para operações I/O concorrentes. Lock protege dados partilhados. t.join() espera terminar. O GIL impede paralelismo real em CPU — para CPU-bound use multiprocessing.

concurrent.futures
from concurrent.futures import (
    ThreadPoolExecutor,
    ProcessPoolExecutor,
    as_completed
)

# ThreadPool (I/O-bound)
def fetch(url):
    import requests
    return requests.get(url).status_code

with ThreadPoolExecutor(max_workers=5) as pool:
    urls = ["http://a.com", "http://b.com"]
    results = list(pool.map(fetch, urls))

# ProcessPool (CPU-bound)
def cpu_work(n):
    return sum(i*i for i in range(n))

with ProcessPoolExecutor() as pool:
    futures = [pool.submit(cpu_work, n) for n in nums]
    for f in as_completed(futures):
        print(f.result())

# as_completed: resultados por ordem de conclusão

ThreadPoolExecutor para I/O (HTTP, ficheiros). ProcessPoolExecutor para CPU (cálculos). pool.map() aplica função a lista. as_completed() retorna por ordem de conclusão. API de alto nível sobre threads/processos.

Web e APIs


9 cards
requests
import requests

# GET
r = requests.get("https://api.exemplo.com/users")
r.status_code   # 200
r.json()        # parse JSON
r.headers       # headers
r.text          # corpo raw

# Com parâmetros
r = requests.get(url, params={"page": 1, "limit": 10})

# POST
r = requests.post(url, json={"nome": "Ana"})
r = requests.post(url, data={"nome": "Ana"})

# Headers e auth
r = requests.get(url, headers={"Authorization": f"Bearer {token}"})

# Timeout e sessão
r = requests.get(url, timeout=5)
with requests.Session() as s:
    s.get(url)  # reutiliza conexão

requests é a biblioteca HTTP padrão. .json() faz parse automático. params para query string. json= envia JSON com header correto. timeout evita hangs. Session() reutiliza conexões.

Web Scraping
import requests
from bs4 import BeautifulSoup

# Fetch + parse
r = requests.get("https://exemplo.com")
soup = BeautifulSoup(r.text, "html.parser")

# Seletores CSS
titulo = soup.select_one("h1").text
links = soup.select("a.link")
for a in links:
    print(a["href"], a.text)

# Por tag e atributo
soup.find("div", class_="conteudo")
soup.find_all("p", limit=5)

# lxml (mais rápido)
soup = BeautifulSoup(r.text, "lxml")

# Para sites dinâmicos: selenium / playwright
# from playwright.sync_api import sync_playwright

BeautifulSoup faz parse de HTML. select() usa seletores CSS. find()/find_all() por tag/atributo. lxml é parser mais rápido. Para JavaScript dinâmico: playwright ou selenium.

httpx e Async HTTP
import httpx

# Síncrono (como requests)
r = httpx.get("https://api.exemplo.com/users")
r.json()

# Async
async def fetch_all(urls):
    async with httpx.AsyncClient() as client:
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [r.json() for r in responses]

# Com timeout e headers
async with httpx.AsyncClient(
    timeout=10.0,
    headers={"Authorization": f"Bearer {token}"},
) as client:
    r = await client.post(
        url,
        json={"nome": "Ana"},
    )

# HTTP/2
async with httpx.AsyncClient(http2=True) as c:
    r = await c.get(url)

httpx é alternativa moderna a requests com suporte async. AsyncClient para requisições concorrentes. Suporta HTTP/2. API compatível com requests. Ideal para FastAPI e aplicações assíncronas. asyncio.gather() para múltiplas requests.

Flask — API Básica
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route("/api/users", methods=["GET"])
def listar_users():
    users = [{"id": 1, "nome": "Ana"}]
    return jsonify(users)

@app.route("/api/users", methods=["POST"])
def criar_user():
    dados = request.get_json()
    nome = dados.get("nome")
    return jsonify({"id": 2, "nome": nome}), 201

@app.route("/api/users/<int:user_id>")
def obter_user(user_id):
    return jsonify({"id": user_id})

if __name__ == "__main__":
    app.run(debug=True, port=5000)

Flask é micro-framework web. @app.route() define endpoints. jsonify() retorna JSON. request.get_json() lê body. methods=["POST"] define verbos HTTP. debug=True para desenvolvimento.

Variáveis de Ambiente
import os
from pathlib import Path

# Ler variável
db_host = os.environ.get("DB_HOST", "localhost")
api_key = os.environ["API_KEY"]  # KeyError se não existir

# python-dotenv
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv()  # carrega .env

# .env:
# DB_HOST=localhost
# DB_PORT=5432
# SECRET_KEY=abc123

# Config pattern
class Config:
    DB_HOST = os.getenv("DB_HOST", "localhost")
    DB_PORT = int(os.getenv("DB_PORT", "5432"))
    DEBUG = os.getenv("DEBUG", "false").lower() == "true"

config = Config()

os.environ.get() lê variáveis de ambiente. python-dotenv carrega ficheiro .env. Nunca faça commit de .env (adicione ao .gitignore). Padrão: classe Config com defaults. os.getenv() = os.environ.get().

FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    nome: str
    email: str
    idade: int | None = None

@app.get("/users/{user_id}")
async def obter_user(user_id: int):
    if user_id > 100:
        raise HTTPException(404, "Não encontrado")
    return {"id": user_id}

@app.post("/users", status_code=201)
async def criar_user(user: UserCreate):
    return {"id": 1, **user.model_dump()}

# Executar: uvicorn main:app --reload
# Docs automáticos: /docs (Swagger)

FastAPI é moderno e assíncrono. Pydantic valida dados automaticamente. Type hints geram validação e docs. HTTPException para erros. Docs automáticos em /docs. Execute com uvicorn.

HTTP Methods e Status
# Verbos HTTP
# GET    - ler recurso
# POST   - criar recurso
# PUT    - substituir recurso
# PATCH  - atualização parcial
# DELETE - remover recurso

# Códigos de status
# 200 OK
# 201 Created
# 204 No Content
# 400 Bad Request
# 401 Unauthorized
# 403 Forbidden
# 404 Not Found
# 422 Unprocessable Entity
# 500 Internal Server Error

# Em Flask
from flask import jsonify
return jsonify({"erro": "Não encontrado"}), 404

# Em FastAPI
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Não encontrado")

Verbos HTTP: GET ler, POST criar, PUT substituir, PATCH parcial, DELETE remover. Códigos: 2xx sucesso, 4xx erro do cliente, 5xx erro do servidor. 401 = não autenticado, 403 = sem permissão.

JSON API — Boas Práticas
# Respostas padronizadas
def resposta_ok(dados, msg="Sucesso"):
    return {"status": "ok", "message": msg, "data": dados}

def resposta_erro(msg, codigo=400):
    return {"status": "error", "message": msg}, codigo

# Validação com Pydantic
from pydantic import BaseModel, EmailStr, field_validator

class UserIn(BaseModel):
    nome: str
    email: EmailStr
    idade: int

    @field_validator("idade")
    @classmethod
    def idade_valida(cls, v):
        if v < 0 or v > 150:
            raise ValueError("Idade inválida")
        return v

# Paginação
def paginar(items, page=1, per_page=20):
    start = (page - 1) * per_page
    return items[start:start + per_page]

APIs: respostas padronizadas com status, message, data. Pydantic valida input. @field_validator para regras custom. Paginação com page/per_page. Códigos HTTP corretos: 200, 201, 400, 404, 500.

Testing com pytest
import pytest

# Teste básico
def test_soma():
    assert soma(2, 3) == 5

# Com fixtures
@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as c:
        yield c

def test_listar(client):
    resp = client.get("/api/users")
    assert resp.status_code == 200
    assert len(resp.json) > 0

# Parametrize
@pytest.mark.parametrize("n,esperado", [
    (1, 1), (2, 4), (3, 9),
])
def test_quadrado(n, esperado):
    assert quadrado(n) == esperado

# Exceções
with pytest.raises(ValueError):
    dividir(1, 0)

pytest é o framework de testes padrão. assert simples (sem self.assertEqual). @pytest.fixture para setup partilhado. @pytest.mark.parametrize testa múltiplos casos. pytest.raises() verifica exceções. Execute com pytest -v.

Instalação e Setup


9 cards
Instalar Python
# Windows:
# 1. Download em python.org/downloads
# 2. Marcar "Add Python to PATH" (importante!)
# 3. Instalar

# macOS (Homebrew):
brew install python

# Linux (Ubuntu/Debian):
sudo apt update
sudo apt install python3 python3-pip

# Verificar instalação:
python --version     # Windows
python3 --version    # macOS/Linux
pip --version

Descarrega o instalador oficial em python.org. No Windows marca Add Python to PATH para usar python no terminal. No macOS usa Homebrew. No Linux usa apt. python3 é o comando padrão em sistemas Unix.

Ambientes Virtuais (venv)
# Criar ambiente virtual:
python -m venv .venv

# Ativar:
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate

# O prompt muda: (.venv) $
# Instalar pacotes isolados:
pip install requests

# Desativar:
deactivate

# Cada projeto deve ter o seu
# próprio ambiente virtual!

venv cria ambientes isolados — cada projeto tem as suas próprias dependências sem conflitos. python -m venv .venv cria. activate ativa (o prompt mostra (.venv)). deactivate desativa. Essencial para projetos reais.

pyproject.toml (moderno)
# pyproject.toml (padrão moderno):
[project]
name = "meu-projeto"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
    "requests>=2.31",
    "flask>=2.3",
]

[project.optional-dependencies]
dev = ["pytest", "black", "ruff"]

# Poetry (alternativa ao pip+venv):
# pip install poetry
poetry new meu-projeto
poetry add requests
poetry install
poetry run python main.py

# Instala dependências de dev:
poetry install --with dev

pyproject.toml é o padrão moderno de configuração (substitui setup.py). Poetry gere dependências e ambientes virtuais de forma integrada. poetry add instala e regista. Alternativas: uv (muito rápido) e pdm. Projetos novos devem usar pyproject.toml.

Primeiro Script
# ola.py
print("Olá, Mundo!")

# Executar no terminal:
# python ola.py        (Windows)
# python3 ola.py       (macOS/Linux)

# Script com entrada do utilizador:
nome = input("Qual é o teu nome? ")
print(f"Olá, {nome}!")

# Comentários começam com #
# Python usa indentação (4 espaços)
# em vez de chaves {}

Cria um ficheiro .py e executa com python ficheiro.py. print() mostra output. input() lê do teclado. f-strings (f"...{var}") interpolam variáveis. A indentação define os blocos de código.

requirements.txt
# requirements.txt (exemplo):
requests==2.31.0
flask>=2.3.0
numpy~=1.24
django

# Operadores de versão:
# ==  versão exata
# >=  maior ou igual
# <=  menor ou igual
# ~=  compatível (mesmo minor)
# !=  diferente de

# Gerar a partir do ambiente:
pip freeze > requirements.txt

# Instalar tudo:
pip install -r requirements.txt

requirements.txt lista as dependências do projeto com versões. == fixa versão exata. >= permite mais recentes. ~= permite apenas patches. Gera com pip freeze e instala com pip install -r. Garante reprodutibilidade.

REPL Interativo
# Iniciar REPL (Read-Eval-Print Loop):
# python        (Windows)
# python3       (macOS/Linux)

>>> 2 + 3
5
>>> "python".upper()
'PYTHON'
>>> import math
>>> math.sqrt(16)
4.0
>>> exit()      # ou Ctrl+D para sair

# IPython (REPL melhorado):
# pip install ipython
# ipython
# Suporta autocomplete, histórico,
# comandos mágicos (%timeit, %run)

O REPL permite testar código linha a linha — ideal para experimentar. >>> é o prompt. exit() ou Ctrl+D para sair. IPython é uma versão mais poderosa com autocomplete e comandos mágicos.

IDEs e Editores
# VS Code (recomendado):
# 1. Instalar extensão "Python" (Microsoft)
# 2. Selecionar interpretador:
#    Ctrl+Shift+P > "Python: Select Interpreter"
# 3. Executar: botão ▶ ou Ctrl+F5

# PyCharm (IDE completo):
# Community (grátis) ou Professional
# Debugging, testes e frameworks integrados

# Jupyter Notebook (dados/ciência):
pip install notebook
jupyter notebook
# Abre no browser — células interativas

# Executar do terminal:
python script.py

VS Code com a extensão Python é a escolha mais popular. PyCharm é uma IDE completa com debugging avançado. Jupyter Notebook é ideal para ciência de dados com células interativas. Todos suportam autocomplete e linting.

pip (Gestor de Pacotes)
# Instalar pacote:
pip install requests
pip install numpy pandas matplotlib

# Versão específica:
pip install django==4.2
pip install "flask>=2.0"

# Atualizar:
pip install --upgrade requests

# Listar instalados:
pip list
pip show requests

# Remover:
pip uninstall requests

# Guardar/instalar dependências:
pip freeze > requirements.txt
pip install -r requirements.txt

pip é o gestor de pacotes padrão do Python. pip install instala do PyPI. pip freeze > requirements.txt exporta as dependências. pip install -r reinstala-as. Usa pip3 em sistemas Unix com Python 2 e 3.

Estrutura de Projeto
meu_projeto/
├── .venv/              # ambiente virtual
├── meu_projeto/        # pacote principal
│   ├── __init__.py
│   ├── main.py
│   └── utils.py
├── tests/              # testes
│   └── test_main.py
├── requirements.txt    # dependências
├── .gitignore          # ignorar .venv/
└── README.md

# __init__.py marca a pasta
# como um pacote Python

# .gitignore essencial:
# .venv/
# __pycache__/
# *.pyc

Estrutura padrão: pasta do pacote com __init__.py, tests/ para testes, requirements.txt para dependências. .venv/ nunca vai para o git. __pycache__/ são ficheiros compilados — ignorar. __init__.py marca a pasta como pacote.