Cheatsheet Python
Linguagem de programação versátil e fácil de aprender
Python
Sintaxe Básica
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) # TrueTipos 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.14Conversõ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:
passis 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, 3print() 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" % nomef-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 0None é 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
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) # TruePython 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
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, 2Funçõ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) # 10Funçõ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 decoratorDecorators 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âneoRecursã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
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] # 4Dunder 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):
passHeranç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
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 elementosCounter 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
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 ityield 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 6Iteradores 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
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) # recursivopathlib.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 contextmanagerwith 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 desativaassert 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
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
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: # melhorAnti-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, isortPEP 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éricostimeit 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
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 formatadoTemplate é 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
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 iasync 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-boundThread 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ãoThreadPoolExecutor 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
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ãorequests é 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_playwrightBeautifulSoup 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
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 devpyproject.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.