Referência de Python
Sintaxe, exemplos e explicações curtas — das bases da linguagem aos padrões algorítmicos, classes e testes.
Fundamentos da linguagem13
n = 42
ratio = 1.5
name = "Ada"
flag = True
nothing = NoneO tipo vem do valor; não é preciso declarar. Verifique com type(x) ou isinstance(x, int).
f"{name} solved {n} tasks"
f"{ratio:.2f}"
f"{n:>5}"
f"{n=}"Interpola expressões numa string. :.2f arredonda em duas casas, :>5 alinha, {n=} imprime nome e valor.
if items:
...
if not s:
...
bool(0), bool(""), bool([]) # FalseColeções vazias, 0, "" e None são falsos. Prefira `if items` a `if len(items) > 0`.
a, b = b, a
first, *rest = [1, 2, 3, 4]
x = y = 0Troca sem variável temporária; *rest absorve o resto da sequência.
sign = "+" if n >= 0 else "-"Condicional em uma linha. Devolve um valor, então pode ser atribuída ou retornada.
7 // 2 # 3
7 % 2 # 1
divmod(7, 2) # (3, 1)
-7 // 2 # -4// arredonda para baixo, não para zero: -7 // 2 é -4. divmod devolve quociente e resto juntos.
len([10, 20, 30]) # 3`len()` é uma função embutida que usa o protocolo `__len__`, por isso funciona de maneira uniforme com diferentes contêineres. Python não possui o método `.len()`.
print("a", "b", 3, sep="-", end="\n")`print()` aceita vários valores, insere `sep` entre eles e adiciona `end` depois do último. Por padrão, usa um espaço e uma quebra de linha.
n = int(input())`input()` sempre retorna uma string, mesmo quando o usuário digita um número. `int()` lança `ValueError` se a string não representar um inteiro válido.
s = str(42)
n = int("42")
x = float("3.5")
h = int("ff", 16)`str`, `int` e `float` criam valores do tipo solicitado, enquanto o segundo argumento de `int()` define a base numérica. Uma string inválida causa `ValueError`.
isinstance(True, int) # True
type(True) is int # False`isinstance()` considera herança e normalmente é a melhor forma de verificar um tipo. `type(x) is T` aceita apenas uma correspondência exata.
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items`None` é um marcador seguro quando um objeto mutável precisa ser criado separadamente em cada chamada. Verifique com `is None`, não com `== None`.
a = [1]
b = [1]
a == b # True
a is b # False
x = 1
y = 1
x is y # True`==` compara valores, enquanto `is` verifica se duas referências apontam para o mesmo objeto. Inteiros pequenos podem ser internados, portanto `is` não deve ser usado para comparar números.
Controle de fluxo10
if score >= 90:
grade = "A"
elif score >= 75:
grade = "B"
else:
grade = "C"As condições são verificadas de cima para baixo, e o primeiro ramo correspondente é executado. Uma condição ampla pode esconder outra mais específica abaixo dela.
a == b
a != b
a < b
a <= b
a is None
0 <= x < n
a < b == c
x not in blockedUma cadeia de comparações funciona como várias verificações ligadas por and, mas a expressão intermediária é avaliada uma única vez. Compare com None usando is ou is not.
value = cached or compute()
result = ready and data
flag = not itemsand e or usam avaliação de curto-circuito e retornam um dos operandos, não necessariamente um bool. O operador not sempre retorna um bool.
for item in items:
process(item)
while left < right:
left += 1for percorre um iterável, enquanto while se repete enquanto a condição for verdadeira. Um while deve alterar o estado relevante ou pode nunca terminar.
range(stop)
range(start, stop)
range(start, stop, step)
range(n - 1, -1, -1)range cria uma sequência preguiçosa de inteiros e não inclui stop. Para percorrer para trás, use um step negativo; o passo zero é inválido.
for x in items:
if x == target:
breakbreak encerra imediatamente o laço mais próximo. Em laços aninhados, o laço externo continua.
for x in items:
if x < 0:
continue
process(x)continue ignora o restante da iteração atual e passa para a próxima. Em um while, atualize antes as variáveis que controlam o laço.
for x in items:
if x == target:
found = True
break
else:
found = FalseO bloco else de um laço é executado apenas quando o laço termina sem break. Ele também é executado quando o iterável está vazio.
match token:
case 0:
kind = "zero"
case int() if token > 0:
kind = "positive"
case _:
kind = "other"match seleciona o primeiro padrão correspondente, enquanto _ funciona como caso genérico. A execução não continua automaticamente para os casos seguintes.
for key, value in pairs:
process(key, value)
for i, (x, y) in enumerate(points):
process(i, x, y)O alvo do laço pode desempacotar estruturas aninhadas diretamente. A quantidade e o formato dos valores devem coincidir, salvo quando um alvo com asterisco é usado.
Funções10
def bounds(values):
return min(values), max(values)
low, high = bounds(items)Vários valores retornados são, na prática, empacotados em uma tupla. Ao desempacotar, a quantidade de destinos deve coincidir com a de elementos.
def power(base, exponent=2):
return base ** exponentO valor padrão é usado quando o argumento é omitido. A expressão padrão é avaliada uma única vez quando a função é criada.
def add_item(item, items=[]):
items.append(item)
return itemsUm objeto mutável padrão é compartilhado entre chamadas, então os dados podem se acumular inesperadamente. Normalmente, use None e crie o objeto dentro da função.
def collect(first, *args, **kwargs):
return first, args, kwargs*args reúne argumentos posicionais extras em uma tupla, enquanto **kwargs reúne argumentos nomeados em um dicionário. Os parâmetros comuns são associados primeiro.
def clamp(value, *, low=0, high=100):
return max(low, min(value, high))Parâmetros após * só podem ser passados pelo nome. Isso evita erros causados pela ordem pouco clara de argumentos semelhantes.
key = lambda item: item[1]
result = sorted(items, key=key)lambda cria uma pequena função a partir de uma única expressão e não aceita instruções comuns. Para lógica mais complexa, prefira def.
def make_adder(delta):
def add(value):
return value + delta
return addUm closure mantém acesso aos nomes da função externa mesmo depois que ela retorna. Os nomes são capturados por referência, o que pode causar surpresas de associação tardia em laços.
count = 0
def outer():
total = 0
def update():
global count
nonlocal total
count += 1
total += 1
return total
return updateglobal modifica um nome no nível do módulo, enquanto nonlocal modifica um nome do escopo externo mais próximo que já o define. Sem essas declarações, uma atribuição cria um nome local.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)lru_cache armazena resultados de chamadas e é especialmente útil para subproblemas recursivos repetidos. Os argumentos devem ser hashable, e maxsize=None cria um cache sem limite.
def binary_search(a: list[int], target: int) -> int | None:
...As anotações descrevem os tipos esperados dos parâmetros e do retorno, mas não os validam em tempo de execução por si só. X | None significa que o resultado pode estar ausente.
Strings7
s[1:4]
s[:3]
s[-2:]
s[::-1]
s[::2]O limite direito não entra. s[::-1] inverte a string, s[::2] pega um caractere a cada dois, índices negativos contam do fim.
"a,b,c".split(",") # ["a", "b", "c"]
"a b c".split()
",".join(["a", "b"]) # "a,b"split() sem argumento divide por espaços e descarta vazios. join junta as partes de volta.
s.strip()
s.lower()
s.upper()
s.replace("a", "b")
s.startswith("ab")
s.endswith(".py")
s.count("a")
s.find("a") # -1Strings são imutáveis: os métodos retornam outra. find devolve -1 se não achar; index levanta erro.
s.isdigit()
s.isalpha()
s.isalnum()
c.isupper()
c.islower()
"ABC123".isupper() # True
"123".isupper() # False
"ab" in sisdigit/isalpha/isalnum são verdadeiros só para uma string não vazia que combina inteira. isupper/islower verificam todos os caracteres com caixa: dígitos e sinais são ignorados, mas é preciso ao menos um deles. `in` verifica se um caractere ou substring está presente.
sorted(s)
sorted(s1) == sorted(s2)
set("aab") == set("ab") # True
sorted("aab") == sorted("ab") # Falsesorted(s) devolve os caracteres em ordem, então comparar duas dessas listas é o teste clássico de anagrama. Um set não serve: perde quantas vezes cada caractere aparece. As duas formas consideram caixa, espaços e sinais.
parts = []
for w in words:
parts.append(w)
result = "".join(parts)Não construa strings com += em laço: cada passo copia tudo. Acumule numa lista e junte no fim.
ord('a') # 97
chr(97) # 'a'
ord(c) - ord('a')Código do caractere e volta. ord(c) - ord("a") dá o índice da letra — truque comum para contador de 26.
Strings: avançado10
"{name}: {score:.2f}".format(name="Ada", score=9.876)
score = 9.876
f"{score:.2f}"`str.format` substitui valores em um modelo, enquanto uma f-string avalia expressões diretamente no escopo atual. Os especificadores controlam precisão, sinal e representação, e chaves literais são escritas como `{{` e `}}`.
x = "42"
f"{x:>8}"
f"{x:<8}"
f"{x:*^8}"Os especificadores `<`, `>` e `^` alinham um valor à esquerda, à direita ou ao centro, e o caractere anterior define o preenchimento. A largura é mínima e não corta um valor longo demais.
table = str.maketrans({"a": "@", "e": "3"})
"peace".translate(table)`str.maketrans` cria uma tabela de transformação, e `translate` a aplica a todos os caracteres em uma única passagem. Um valor `None` na tabela remove o caractere correspondente.
head, sep, tail = "key=value".partition("=")`partition` divide apenas na primeira ocorrência e sempre retorna três elementos, incluindo o próprio separador. Se o separador não existir, a string original fica no primeiro elemento e os outros dois ficam vazios.
"a\nb\r\nc".splitlines() # ["a", "b", "c"]`splitlines` reconhece diferentes tipos de quebra de linha e as remove por padrão. Diferentemente de `split("\n")`, ele trata corretamente vários separadores de linha padrão.
"42".zfill(5) # "00042"
"-42".zfill(5) # "-0042"`zfill` preenche uma string com zeros à esquerda até uma largura mínima e coloca os zeros depois do sinal numérico. Ele não corta uma string que já seja maior que a largura solicitada.
"unhappy".removeprefix("un") # "happy"
"report.csv".removesuffix(".csv") # "report"`removeprefix` e `removesuffix` removem uma correspondência exata apenas da extremidade correspondente da string. Diferentemente de `strip`, o argumento não é interpretado como um conjunto de caracteres individuais.
import re
re.match(r"\d+", text)
re.search(r"\d+", text)
re.findall(r"\d+", text)
re.sub(r"\s+", " ", text)`match` verifica apenas o início da string, `search` encontra a primeira correspondência em qualquer posição, `findall` coleta todas e `sub` as substitui. Um padrão usado repetidamente normalmente deve ser criado uma vez com `re.compile`.
data = "café".encode("utf-8")
text = data.decode("utf-8")`encode` converte uma `str` em `bytes`, enquanto `decode` realiza a conversão inversa usando uma codificação especificada. Usar codificações incompatíveis pode gerar um erro ou corromper o texto.
"Straße".lower() # "straße"
"Straße".casefold() # "strasse"`lower` realiza uma conversão normal para minúsculas, enquanto `casefold` aplica um dobramento de caixa mais agressivo (ß → ss) para comparações sem diferenciar maiúsculas. Não é normalização Unicode: um `é` pré-composto e `e` com acento combinante continuam diferentes após `casefold` — para isso existe `unicodedata.normalize`.
Listas e fatias13
a = [0, 1, 2, 3, 4]
a[1:4] # [1, 2, 3]
a[:3]
a[-2:]
a[::-1]
b = a[:]Uma fatia cria nova lista. a[:] copia só o nível de cima — listas aninhadas continuam compartilhadas.
a.append(x) # O(1)
a.extend([x, y])
a.insert(0, x) # O(n)
a.pop() # O(1)
a.pop(0) # O(n)Inserir ou remover no início custa O(n). Se precisa das duas pontas, use collections.deque.
for i, x in enumerate(a):
...
for i, x in enumerate(a, start=1):
...Índice e valor juntos. Substitui for i in range(len(a)) e evita o erro de um a mais.
for x, y in zip(a, b):
...
list(zip(a, b))
zip(*matrix)Percorre várias sequências em paralelo e para na mais curta. zip(*matrix) transpõe uma matriz.
[x for x in a if x > 0]
[x * 2 for x in a]
[y for row in m for y in row]
{x: x ** 2 for x in a}Mais curto e rápido que laço com append. Dois for achatam uma lista aninhada; chaves com dois-pontos criam um dicionário. Os for aninhados seguem a ordem de um laço comum.
any(x > 0 for x in a)
all(x > 0 for x in a)
sum(a)
min(a)
max(a)
max(a, key=abs)
max(a, default=0)any/all param no primeiro elemento decisivo. key escolhe o que comparar; default evita erro em lista vazia.
grid = [[0] * m for _ in range(n)] # ✓
grid = [[0] * m] * n # ✗A segunda forma está errada: multiplicar lista copia a referência, não o conteúdo, e todas as linhas viram a mesma. Use compreensão.
a = [1, 2, 3]
b = list(range(3))
zeros = [0] * 5
rows = [[0] * 3] * 2Um literal ou `list()` cria uma lista, enquanto a multiplicação repete seus elementos. Repetir um objeto mutável aninhado copia referências, então as linhas de `rows` ficam vinculadas.
a = [4, 2, 4]
i = a.index(4)
n = a.count(4)`index()` retorna a posição da primeira ocorrência, enquanto `count()` retorna o número de ocorrências. `index()` lança `ValueError` quando o valor não existe.
a = [10, 20, 30, 20]
a.remove(20)
del a[0]
x = a.pop()`remove()` exclui o primeiro valor correspondente, `del` exclui por índice ou fatia e `pop()` exclui e retorna um elemento. Um valor ausente em `remove()` ou um índice inválido causa uma exceção.
from copy import deepcopy
a = [[1], [2]]
b = a.copy()
c = deepcopy(a)
a[0].append(9)`copy()` cria uma nova lista externa, mas preserva as referências aos objetos aninhados. `deepcopy()` copia recursivamente o conteúdo aninhado, porém normalmente usa mais tempo e memória.
def add(a, b):
return a + b
nums = [2, 3]
result = add(*nums)O operador `*` passa os elementos da lista como argumentos posicionais separados. A quantidade deve corresponder à assinatura da função, a menos que ela aceite `*args`.
point = (2, 3)
seen = {point}Uma tupla se parece com uma lista, mas seus elementos não podem ser substituídos, adicionados ou removidos. Ela pode ser armazenada em um conjunto ou usada como chave de dicionário somente se todos os elementos forem hashable.
Dicionários e conjuntos13
d[k] # KeyError
d.get(k) # None
d.get(k, 0)
d.setdefault(k, []).append(x)get não levanta exceção. setdefault cria o valor se a chave falta e já o devolve.
for k in d:
...
for k, v in d.items():
...
d.keys()
d.values()Iterar um dict devolve chaves. A ordem de inserção é mantida desde o Python 3.7.
from collections import Counter
c = Counter(a)
c[x] # 0
c.most_common(2)
Counter(s1) == Counter(s2)Contador de frequências pronto. Chave ausente dá 0, não erro; most_common devolve os mais frequentes.
from collections import defaultdict
g = defaultdict(list)
g[k].append(x)
cnt = defaultdict(int)
cnt[k] += 1O valor é criado no primeiro acesso. Bom para agrupar e para listas de adjacência de grafos.
s = set(a)
s.add(x)
s.discard(x)
x in s # O(1)
a_set & b_set
a_set | b_set
a_set - b_setPertencimento é O(1) contra O(n) da lista. & é interseção, | união, - diferença. Um set não guarda ordem nem duplicatas.
seen = set()
for x in a:
if x in seen:
return True
seen.add(x)Acha duplicata em uma passada, O(n). O mesmo formato resolve two-sum: procure target - x em seen.
d1 = {"a": 1}
d2 = dict(a=1)
d3 = dict([("a", 1)])
d4 = {k: 0 for k in ("a", "b")}Um dicionário pode ser criado com um literal, `dict()`, uma sequência de pares ou uma compreensão de dicionário. Argumentos nomeados de `dict()` funcionam apenas para chaves de texto que sejam identificadores válidos.
d = {"a": 1, "c": 3}
"a" in d # True
x = d.pop("a", 0)
y = d.pop("b", 0)
del d["c"]Em um dicionário, `in` verifica as chaves, não os valores. `del` lança `KeyError` para uma chave ausente, enquanto `pop(key, default)` retorna o valor alternativo.
d = {"a": 1, "b": 2}
keys = d.keys()
values = d.values()
pairs = d.items()
d["c"] = 3
list(keys) # ["a", "b", "c"]Os métodos `keys()`, `values()` e `items()` retornam visualizações dinâmicas, não listas. Elas refletem alterações posteriores no dicionário; para obter uma cópia independente, faça a conversão explícita para `list`.
a = {"x": 1}
b = {"x": 2, "y": 3}
c = a | b
a.update(b)O operador `|` cria um novo dicionário, enquanto `update()` modifica o existente. Quando as chaves coincidem, prevalece o valor do dicionário da direita ou da fonte fornecida.
d = {"a": 3, "b": 1, "c": 2}
pairs = sorted(d.items(), key=lambda item: item[1])Para ordenar por valores, ordene os pares de `items()` usando o segundo elemento como chave. O resultado é uma lista de tuplas, não um dicionário automaticamente ordenado.
freq = {}
for x in [2, 1, 2]:
freq[x] = freq.get(x, 0) + 1As frequências podem ser contadas com um dicionário comum usando zero para uma chave ainda não encontrada. Sem um valor padrão, `get()` retorna `None`, que não pode ser somado a um número.
edge = frozenset({2, 5})
weights = {edge: 7}Um `frozenset` é imutável e hashable, portanto pode ser usado como chave de dicionário ou elemento de outro conjunto. Um `set` comum não pode ser usado dessa forma.
Coleções da biblioteca padrão10
from collections import deque
q = deque([2, 3])
q.appendleft(1)
q.append(4)
left = q.popleft()
right = q.pop()deque permite adicionar e remover elementos nas duas extremidades em O(1). Para uma fila, use append e popleft em vez de pop(0) de uma lista.
from collections import deque
q = deque([1, 2, 3], maxlen=3)
q.append(4)
q.rotate(1)
q.rotate(-1)maxlen descarta automaticamente elementos da extremidade oposta quando o deque fica cheio. rotate desloca para a direita com argumento positivo e para a esquerda com negativo.
import heapq
h = [4, 1, 7, 2]
heapq.heapify(h)
heapq.heappush(h, 0)
smallest = heapq.heappop(h)heapq implementa um heap mínimo, então o menor elemento sempre fica na raiz. heapify executa em O(n), enquanto push e pop custam O(log n).
import heapq
nums = [4, 1, 7, 2]
largest = heapq.nlargest(2, nums)
smallest = heapq.nsmallest(2, nums)
h = [-x for x in nums]
heapq.heapify(h)
maximum = -heapq.heappop(h)nlargest e nsmallest selecionam os k elementos extremos sem ordenar toda a entrada. Um heap máximo costuma ser simulado armazenando os números com sinal invertido.
from collections import Counter
a = Counter("banana")
a.update("band")
top = a.most_common(2)
b = Counter("an")
added = a + b
common = a & bCounter armazena frequências e oferece most_common, update e operações entre contadores. As operações aritméticas criam um novo Counter e geralmente omitem resultados zero ou negativos.
from collections import defaultdict
groups = defaultdict(list)
counts = defaultdict(int)
seen = defaultdict(set)
groups["a"].append(1)
value = counts["missing"]defaultdict cria um valor com sua fábrica ao acessar uma chave ausente. A leitura com colchetes altera o dicionário, então use in ou get quando quiser apenas verificar.
from collections import OrderedDict
d = {"a": 1, "b": 2}
od = OrderedDict(d)
od.move_to_end("a")
first = od.popitem(last=False)O dict comum já preserva a ordem de inserção, então OrderedDict é útil principalmente para reordenar e remover de uma extremidade específica. A igualdade entre dois OrderedDict também considera a ordem.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(2, 3)
x = p.xnamedtuple cria um tipo compacto e imutável com acesso aos campos por nome e índice. Para modelos mais complexos com métodos e valores padrão, dataclass costuma ser mais conveniente.
from array import array
nums = array("i", [1, 2, 3])
raw = bytes([65, 66, 67])
mutable = bytearray(raw)
mutable[0] = 90array armazena valores de um único tipo primitivo de forma mais compacta que uma lista, mas raramente é necessário em problemas algorítmicos. bytes é imutável, enquanto bytearray permite alterar bytes individuais.
from collections import ChainMap
defaults = {"timeout": 30}
overrides = {"timeout": 10}
config = ChainMap(overrides, defaults)
value = config["timeout"]ChainMap pesquisa em vários dicionários da esquerda para a direita sem copiá-los ou mesclá-los. As atribuições modificam apenas o primeiro dicionário por padrão.
Iteração e geradores10
squares_list = [x * x for x in values]
squares_gen = (x * x for x in values)Uma compreensão de lista cria imediatamente a lista inteira, enquanto uma expressão geradora produz valores sob demanda. O gerador economiza memória, mas normalmente só pode ser consumido uma vez.
def countdown(n):
while n > 0:
yield n
n -= 1O uso de yield transforma a função em uma função geradora: a chamada retorna um gerador, e o corpo é executado durante a iteração. Após cada yield, a execução continua da posição salva.
def flatten(chunks):
for chunk in chunks:
yield from chunkyield from repassa os itens de um iterável aninhado sem exigir um laço interno manual. Cada uso achata apenas um nível.
it = iter([10])
first = next(it)
missing = next(it, None)
try:
next(it)
except StopIteration:
...iter obtém um iterador, enquanto next recupera o próximo item e avança seu estado. Fornecer um valor padrão evita StopIteration quando o iterador se esgota.
from itertools import count, cycle, islice
numbers = list(islice(count(10, 2), 4))
pattern = list(islice(cycle("ab"), 5))count e cycle criam iteradores potencialmente infinitos, enquanto islice os limita sem materializar a sequência inteira. islice não aceita índices negativos nem passo negativo.
from itertools import chain
merged = list(chain([1, 2], [3, 4]))
flat = list(chain.from_iterable([[1, 2], [3, 4]]))chain percorre preguiçosamente vários iteráveis um após o outro. chain.from_iterable recebe um iterável externo e achata um nível.
from itertools import combinations, permutations, product
pairs = list(product([0, 1], repeat=2))
orders = list(permutations([1, 2, 3], 2))
choices = list(combinations([1, 2, 3], 2))product cria um produto cartesiano, permutations considera a ordem e combinations não. Elas retornam iteradores, mas a quantidade de resultados pode crescer de forma combinatória.
from itertools import groupby
items = [1, 1, 2, 2, 2, 3]
groups = [(key, list(group)) for key, group in groupby(items)]groupby agrupa apenas itens consecutivos com a mesma chave. Para um agrupamento global, normalmente ordene os dados primeiro pela mesma chave.
a = [1, 2, 3]
it = reversed(a)
b = list(it)reversed retorna um iterador reverso e não modifica a sequência original. O objeto deve aceitar iteração reversa ou fornecer acesso por índice e comprimento.
total = sum(x * x for x in values)
has_negative = any(x < 0 for x in values)
all_even = all(x % 2 == 0 for x in values)sum consome todos os valores do gerador, enquanto any e all param assim que o resultado é conhecido. Para uma fonte vazia, any retorna False e all retorna True.
Números e matemática10
n = 10**100
n.bit_length()O tipo `int` armazena inteiros de tamanho arbitrário, portanto não há overflow de largura fixa. Os limites práticos são a memória e o tempo de processamento, mas desde o Python 3.11 a conversão entre `int` e `str` é limitada a 4300 dígitos e gera `ValueError` acima disso.
0.1 + 0.2 == 0.3 # False
x = float("inf")O tipo `float` usa aproximações binárias, então muitas frações decimais não podem ser representadas exatamente. Use `math.isclose` para comparações e `decimal.Decimal` quando precisar de aritmética decimal exata.
round(2.5) # 2
round(3.5) # 4
round(12.345, 2)`round` resolve um empate exato para o valor par mais próximo, em vez de sempre arredondar para cima. O resultado com valores `float` também pode ser afetado pelo erro de representação binária.
abs(-7) # 7
2**10 # 1024
pow(2, 10) # 1024
pow(2, 10, 1000) # 24`abs` retorna o valor absoluto, enquanto `**` e `pow` realizam exponenciação. A forma `pow(a, b, mod)` calcula uma potência modular de maneira eficiente sem criar um inteiro intermediário enorme.
from math import sqrt, isqrt, gcd, factorial
sqrt(81) # 9.0
isqrt(80) # 8
gcd(18, 24) # 6
factorial(5) # 120`sqrt` retorna uma raiz de ponto flutuante, enquanto `isqrt` retorna a parte inteira exata sem erros de `float`. `gcd` calcula o máximo divisor comum, e `factorial` aceita apenas inteiros não negativos.
from math import ceil, floor, inf
ceil(2.1) # 3
floor(2.9) # 2
inf > 10**100 # True`ceil` arredonda em direção ao infinito positivo, enquanto `floor` arredonda em direção ao infinito negativo, o que é especialmente importante para valores negativos. `math.inf` é comparado como infinito, mas não surge automaticamente em uma divisão por zero.
try:
1 / 0
except ZeroDivisionError:
...Divisão, divisão inteira e módulo com divisor zero geram `ZeroDivisionError`. Python não converte essa divisão em infinito.
int("101", 2) # 5
int("0xff", 0) # 255
int(3.9) # 3
float("2.5") # 2.5
str(42) # "42"
bin(10) # "0b1010"
hex(255) # "0xff"
oct(8) # "0o10"`int` pode interpretar strings usando bases de 2 a 36, enquanto a base `0` detecta automaticamente um prefixo padrão. Converter um `float` para `int` trunca em direção a zero, e `bin`, `hex` e `oct` retornam strings com prefixo.
x = 0b1100
y = 0b1010
x & y # 8
x | y # 14
x ^ y # 6
~x # -13
x << 2 # 48
x >> 2 # 3
x.bit_count() # 2As operações bit a bit trabalham com a representação binária dos inteiros e são frequentemente usadas para máscaras e conjuntos de estados. Os inteiros não têm largura fixa, e `~x` é sempre igual a `-x - 1`.
import random
rng = random.Random(42)
rng.randrange(1, 10)
rng.randint(1, 10)
rng.choice([1, 2, 3])
a = [1, 2, 3]
rng.shuffle(a)`random` gera valores pseudoaleatórios reproduzíveis: `randrange` exclui o limite final, enquanto `randint` inclui os dois extremos. O módulo não é adequado para senhas ou criptografia.
Ordenação e busca6
b = sorted(a)
a.sort()
sorted(a, reverse=True)sorted devolve nova lista; sort ordena no lugar e devolve None. A ordenação é estável.
sorted(words, key=len)
sorted(pairs, key=lambda p: p[1])
sorted(a, key=lambda x: (-x[1], x[0]))key é chamada uma vez por elemento. Uma tupla dá vários níveis; o menos inverte um campo numérico.
a.sort(key=lambda x: x.age)
a.sort(key=lambda x: x.name)Elementos iguais mantêm a ordem, então dá para ordenar primeiro pela chave menos significativa.
from operator import itemgetter
sorted(rows, key=itemgetter(1, 0))Igual a uma lambda, porém mais rápido e mais claro ao ordenar por vários campos.
from bisect import bisect_left, bisect_right, insort
i = bisect_left(a, x)
j = bisect_right(a, x)
insort(a, x)Busca binária em lista ordenada em O(log n). bisect_left dá a primeira posição de x e bisect_right a seguinte, então j - i é a contagem de ocorrências. insort insere mantendo a ordem.
lo, hi = 0, len(a) - 1
while lo <= hi:
mid = (lo + hi) // 2
if a[mid] == x:
return mid
if a[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return -1A lista precisa estar ordenada. Mova os limites além de mid, senão o laço não termina.
Pilha, fila e dois ponteiros9
stack = []
stack.append(1)
stack.append(2)
top = stack.pop()Uma lista implementa eficientemente uma pilha LIFO com append e pop no final em O(1) amortizado. Remover do início de uma lista custa O(n).
from collections import deque
q = deque([1, 2])
q.append(3)
first = q.popleft()Uma fila FIFO é implementada eficientemente com deque: append adiciona à direita e popleft remove à esquerda em O(1). Uma lista com pop(0) não é adequada para filas grandes porque desloca os elementos.
def next_greater(nums):
result = [-1] * len(nums)
stack = []
for i, x in enumerate(nums):
while stack and nums[stack[-1]] < x:
result[stack.pop()] = x
stack.append(i)
return resultUma pilha monotônica mantém valores ou índices ordenados na pilha e encontra o maior ou menor elemento mais próximo em O(n). Armazene índices quando precisar de distâncias ou posições.
def is_balanced(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs and (not stack or stack.pop() != pairs[ch]):
return False
return not stackOs símbolos de abertura são colocados em uma pilha, e cada fechamento deve corresponder ao topo. Ao final, a pilha deve estar vazia ou haverá símbolos não fechados.
def has_pair(nums, target):
left, right = 0, len(nums) - 1
while left < right:
total = nums[left] + nums[right]
if total == target:
return True
if total < target:
left += 1
else:
right -= 1
return FalsePonteiros em extremidades opostas reduzem o espaço de busca quando o resultado atual indica qual deles deve avançar. Para buscar um par com soma específica, o array deve estar ordenado.
def middle_node(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowUm ponteiro rápido avança mais que um lento, permitindo encontrar o meio ou detectar ciclos sem memória extra. Verifique fast e o próximo nó antes de avançar duas posições.
def remove_duplicates(nums):
if not nums:
return 0
write = 1
for read in range(1, len(nums)):
if nums[read] != nums[write - 1]:
nums[write] = nums[read]
write += 1
return writeUm ponteiro de leitura percorre o array, enquanto um de escrita constrói o prefixo válido sem outro array. Esta versão pressupõe que valores iguais estejam adjacentes, normalmente porque a entrada está ordenada.
def reverse_in_place(nums):
left, right = 0, len(nums) - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1Trocar elementos das duas extremidades inverte uma sequência mutável em O(n) e usa O(1) de memória extra. Uma string não pode ser modificada assim porque é imutável.
def merge_sorted(a, b):
i = j = 0
result = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
result.extend(a[i:])
result.extend(b[j:])
return resultDois ponteiros mesclam sequências ordenadas em O(n + m), escolhendo repetidamente o menor elemento atual. Após o laço principal, adicione a parte restante.
Janela deslizante e somas de prefixo9
def window_sums(nums, k):
if k > len(nums):
return []
current = sum(nums[:k])
result = [current]
for right in range(k, len(nums)):
current += nums[right] - nums[right - k]
result.append(current)
return resultUma janela fixa atualiza seu estado adicionando o elemento que entra e removendo o que sai, processando todas as janelas em O(n). Defina o comportamento quando k for zero ou maior que a entrada.
def min_length(nums, target):
left = 0
total = 0
answer = len(nums) + 1
for right, x in enumerate(nums):
total += x
while total >= target:
answer = min(answer, right - left + 1)
total -= nums[left]
left += 1
return 0 if answer > len(nums) else answerUma janela variável cresce pela direita e encolhe pela esquerda enquanto a condição necessária for satisfeita. Versões baseadas em soma geralmente exigem valores não negativos para manter a monotonicidade.
from collections import Counter
def anagram_windows(s, pattern):
need = Counter(pattern)
window = Counter()
result = []
k = len(pattern)
for right, ch in enumerate(s):
window[ch] += 1
if right >= k:
left_ch = s[right - k]
window[left_ch] -= 1
if window[left_ch] == 0:
del window[left_ch]
if window == need:
result.append(right - k + 1)
return resultAs frequências da janela são atualizadas apenas para o elemento que entra e o que sai, evitando novas contagens completas. Remover contagens zero torna as comparações mais claras.
def max_window_sum(nums, k):
current = sum(nums[:k])
best = current
for right in range(k, len(nums)):
current += nums[right] - nums[right - k]
best = max(best, current)
return bestA soma da próxima janela é obtida subtraindo o elemento da esquerda e adicionando o novo elemento da direita. Se houver valores negativos, inicialize o máximo com a primeira janela completa, não com zero.
def range_sum(prefix, left, right):
return prefix[right] - prefix[left]
nums = [2, 4, 1]
prefix = [0]
for x in nums:
prefix.append(prefix[-1] + x)
value = range_sum(prefix, 1, 3)Um array de prefixos com zero inicial permite obter a soma do intervalo semiaberto [left, right) em O(1). O zero adicional simplifica os limites e os intervalos que começam no primeiro elemento.
nums = [3, 8, 10, 15]
diffs = [b - a for a, b in zip(nums, nums[1:])]As diferenças adjacentes descrevem as mudanças entre posições e permitem reconstruir valores por acumulação. O resultado possui um elemento a menos que a sequência original.
from itertools import accumulate
prefix = list(accumulate([2, 4, 1], initial=0))
products = list(accumulate([2, 3, 4], lambda a, b: a * b))accumulate produz de forma preguiçosa os resultados intermediários e aceita uma função binária personalizada. initial adiciona um valor inicial à saída e altera seu comprimento.
def has_subarray_sum(nums, target):
seen = {0}
prefix = 0
for x in nums:
prefix += x
if prefix - target in seen:
return True
seen.add(prefix)
return FalseUm subarray soma target quando duas somas de prefixo diferem por target. Um conjunto de prefixos permite O(n) e funciona com valores negativos, ao contrário de uma janela deslizante baseada em soma.
def first_true(low, high, feasible):
while low < high:
mid = (low + high) // 2
if feasible(mid):
high = mid
else:
low = mid + 1
return lowA busca binária sobre a resposta encontra o limite em que um predicado monotônico muda de False para True. O principal desafio é escolher limites inclusivos consistentes e provar a monotonicidade de feasible.
Recursão e programação dinâmica10
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)O caso base interrompe a recursão, enquanto o passo recursivo reduz o problema a uma instância menor. Sem um caso base alcançável, as chamadas terminam com RecursionError.
import sys
sys.setrecursionlimit(200000)Python limita a profundidade da pilha e gera RecursionError quando ela é excedida. Aumente o limite com cuidado, pois uma recursão muito profunda pode esgotar a pilha do sistema.
def factorial_tail(n, acc=1):
if n <= 1:
return acc
return factorial_tail(n - 1, acc * n)Uma chamada de cauda é a última operação de uma função, mas Python não otimiza essas chamadas. A recursão de cauda ainda consome a pilha e geralmente é substituída por um laço.
from functools import cache, lru_cache
@lru_cache(maxsize=None)
def fib_lru(n):
return n if n < 2 else fib_lru(n - 1) + fib_lru(n - 2)
@cache
def fib_cache(n):
return n if n < 2 else fib_cache(n - 1) + fib_cache(n - 2)Esses decoradores armazenam resultados por argumentos e eliminam cálculos repetidos. cache equivale a um lru_cache sem limite, e todos os argumentos devem ser hashable.
def fib(n):
if n < 2:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]A DP bottom-up calcula estados dos mais simples aos mais complexos e os armazena em um array. Ela evita a pilha de recursão, embora o array às vezes possa ser reduzido a poucas variáveis.
def grid_paths(rows, cols):
dp = [[0] * cols for _ in range(rows)]
for r in range(rows):
dp[r][0] = 1
for c in range(cols):
dp[0][c] = 1
for r in range(1, rows):
for c in range(1, cols):
dp[r][c] = dp[r - 1][c] + dp[r][c - 1]
return dp[-1][-1]Uma tabela bidimensional é usada quando um estado depende de dois parâmetros, como uma posição de linha e coluna. É importante inicializar corretamente as bordas antes de preencher as demais células.
def min_coins(coins, amount):
inf = amount + 1
dp = [0] + [inf] * amount
prev = [-1] * (amount + 1)
for total in range(1, amount + 1):
for coin in coins:
if coin <= total and dp[total - coin] + 1 < dp[total]:
dp[total] = dp[total - coin] + 1
prev[total] = coin
if dp[amount] == inf:
return None
answer = []
while amount > 0:
coin = prev[amount]
answer.append(coin)
amount -= coin
return answerAlém do valor ótimo, a DP pode armazenar a escolha usada para alcançar cada estado. Depois, a resposta é reconstruída percorrendo os estados para trás a partir do estado final.
def merge_sort(a):
if len(a) <= 1:
return a
mid = len(a) // 2
left = merge_sort(a[:mid])
right = merge_sort(a[mid:])
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
return result + left[i:] + right[j:]O problema é dividido em subproblemas independentes, cujas soluções recursivas são combinadas depois. A eficiência depende de uma divisão equilibrada e do custo de combinar os resultados.
def permutations(a):
result = []
def backtrack(start):
if start == len(a):
result.append(a.copy())
return
for i in range(start, len(a)):
a[start], a[i] = a[i], a[start]
backtrack(start + 1)
a[start], a[i] = a[i], a[start]
backtrack(0)
return resultO backtracking escolhe uma opção, continua recursivamente e depois restaura o estado alterado. Gerar todas as permutações exige tempo fatorial.
def subsets(a):
result = []
current = []
def backtrack(index):
if index == len(a):
result.append(current.copy())
return
backtrack(index + 1)
current.append(a[index])
backtrack(index + 1)
current.pop()
backtrack(0)
return resultPara cada elemento, a recursão considera duas escolhas: incluí-lo ou ignorá-lo. O resultado atual deve ser copiado ao ser salvo e restaurado após a chamada recursiva.
Grafos e árvores10
edges = [(0, 1), (0, 2), (1, 2)]
graph = {0: [], 1: [], 2: []}
for u, v in edges:
graph[u].append(v)
graph[v].append(u)Uma lista de adjacência armazena os vizinhos de cada vértice e normalmente exige O(V + E) de memória. Em um grafo direcionado, a aresta inversa não deve ser adicionada automaticamente.
from collections import deque
def bfs(graph, start):
queue = deque([start])
visited = {start}
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return orderA BFS percorre um grafo por níveis usando uma fila e encontra distâncias mínimas em um grafo não ponderado. Marque um vértice como visitado ao colocá-lo na fila para evitar inserções duplicadas.
def dfs_recursive(graph, start):
visited = set()
order = []
def dfs(node):
visited.add(node)
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor)
dfs(start)
return orderA DFS recursiva explora completamente um ramo antes de passar ao próximo. Em cadeias longas, ela pode ultrapassar o limite de recursão do Python.
def dfs_iterative(graph, start):
stack = [start]
visited = set()
order = []
while stack:
node = stack.pop()
if node in visited:
continue
visited.add(node)
order.append(node)
stack.extend(reversed(graph[node]))
return orderA DFS iterativa usa uma pilha explícita e não depende do limite de recursão. A ordem da travessia depende da ordem em que os vizinhos são colocados na pilha.
from collections import deque
def shortest_path(graph, start, target):
queue = deque([start])
parent = {start: None}
while queue:
node = queue.popleft()
if node == target:
break
for neighbor in graph[node]:
if neighbor not in parent:
parent[neighbor] = node
queue.append(neighbor)
if target not in parent:
return None
path = []
node = target
while node is not None:
path.append(node)
node = parent[node]
return path[::-1]A BFS alcança primeiro um vértice por um caminho com o menor número de arestas. Para reconstruir o caminho, basta armazenar o pai de cada vértice visitado pela primeira vez.
def preorder(node, result):
if node is None:
return
result.append(node.val)
preorder(node.left, result)
preorder(node.right, result)
def inorder(node, result):
if node is None:
return
inorder(node.left, result)
result.append(node.val)
inorder(node.right, result)
def postorder(node, result):
if node is None:
return
postorder(node.left, result)
postorder(node.right, result)
result.append(node.val)Preorder processa o nó antes dos filhos, inorder entre as subárvores esquerda e direita, e postorder depois dos filhos. Inorder produz ordem crescente apenas em uma árvore binária de busca válida.
from collections import deque
def level_order(root):
if root is None:
return []
queue = deque([root])
levels = []
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left is not None:
queue.append(node.left)
if node.right is not None:
queue.append(node.right)
levels.append(level)
return levelsA travessia por níveis é uma BFS aplicada a uma árvore e usa uma fila. O tamanho da fila no início da iteração fixa a quantidade de nós do nível atual.
def has_cycle(graph):
state = {}
def dfs(node):
state[node] = 1
for neighbor in graph[node]:
if state.get(neighbor, 0) == 1:
return True
if state.get(neighbor, 0) == 0 and dfs(neighbor):
return True
state[node] = 2
return False
return any(state.get(node, 0) == 0 and dfs(node) for node in graph)Em um grafo direcionado, uma aresta para um vértice que está na pilha da DFS indica um ciclo. Normalmente são usados três estados: não visitado, em processamento e totalmente processado.
from collections import deque
def topological_sort(graph):
indegree = {node: 0 for node in graph}
for node in graph:
for neighbor in graph[node]:
indegree[neighbor] += 1
queue = deque(node for node in graph if indegree[node] == 0)
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return order if len(order) == len(graph) else NoneO algoritmo de Kahn remove repetidamente vértices com grau de entrada zero. Se nem todos os vértices forem processados, o grafo contém um ciclo e não existe ordem topológica.
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
while x != self.parent[x]:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, a, b):
a = self.find(a)
b = self.find(b)
if a == b:
return False
if self.size[a] < self.size[b]:
a, b = b, a
self.parent[b] = a
self.size[a] += self.size[b]
return TrueUnion-Find verifica rapidamente se vértices pertencem ao mesmo componente e une componentes. Compressão de caminhos e união por tamanho fornecem tempo amortizado quase constante por operação.
Exceções10
try:
value = int(text)
except ValueError:
value = 0
else:
value += 1
finally:
closed = Trueexcept é executado após uma exceção, else apenas quando try termina com sucesso, e finally sempre é executado. Coloque em try somente as operações cujas exceções são realmente esperadas.
try:
item = data[key]
except KeyError:
item = NoneCapture um tipo específico de exceção para não esconder erros não relacionados. Um except sem tipo só é adequado em casos raros quando a exceção é tratada corretamente ou relançada.
try:
result = values[index] / divisor
except (IndexError, ZeroDivisionError):
result = NoneVários tipos de exceção com o mesmo tratamento podem ser agrupados em uma tupla em um único except. Use blocos except separados quando o tratamento for diferente.
def load_number(text):
try:
return int(text)
except ValueError as exc:
raise ValueError("invalid number") from exc
def parse_number(text):
try:
return load_number(text)
except ValueError:
raiseraise cria uma exceção, enquanto raise sem argumentos relança a exceção atual. A forma raise ... from ... preserva uma cadeia causal explícita entre exceções.
class InvalidStateError(Exception):
pass
def require_ready(ready):
if not ready:
raise InvalidStateError("not ready")Exceções personalizadas herdam de Exception e distinguem erros de domínio das exceções embutidas do Python. Muitas vezes, uma classe vazia com um nome claro é suficiente.
def built_in_errors(data, key, index, value, divisor):
if not isinstance(value, int):
raise TypeError
if divisor == 0:
raise ZeroDivisionError
if key not in data:
raise KeyError(key)
if not -len(data) <= index < len(data):
raise IndexError(index)
if value < 0:
raise ValueError(value)ValueError indica um valor inválido de um tipo aceitável, enquanto TypeError indica um tipo inválido; KeyError e IndexError representam chaves ou índices inexistentes. ZeroDivisionError ocorre ao dividir ou calcular resto por zero.
def divide(a, b):
assert b != 0
return a / bassert verifica invariantes internos e gera AssertionError quando eles falham. As verificações são removidas quando Python é executado com -O, portanto não devem validar dados do usuário.
with open("data.txt", encoding="utf-8") as file:
text = file.read()Um gerenciador de contexto garante a liberação do recurso após a conclusão normal e após uma exceção. Para arquivos, isso significa fechamento automático ao sair do bloco with.
from contextlib import suppress
from pathlib import Path
path = Path("data.txt")
with suppress(FileNotFoundError):
path.unlink()contextlib.suppress ignora somente os tipos de exceção explicitamente listados e é adequado para casos curtos e esperados. Não o use para esconder falhas que exigem tratamento ou registro.
def eafp(data, key):
try:
return data[key]
except KeyError:
return None
def lbyl(data, key):
if key in data:
return data[key]
return NoneEAFP executa primeiro a operação e trata uma falha esperada, enquanto LBYL verifica uma condição antes. EAFP costuma ser mais idiomático em Python e evita diferenças entre a verificação e a operação.
Classes e objetos10
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def move(self, dx, dy):
self.x += dx
self.y += dyUma classe descreve a estrutura dos objetos, enquanto __init__ inicializa cada nova instância. self é passado automaticamente e representa o objeto atual.
class Counter:
total = 0
def __init__(self):
self.value = 0
Counter.total += 1Um atributo de classe é compartilhado por todas as instâncias, enquanto um atributo em self pertence a um objeto. Alterar um atributo de classe mutável pode afetar inesperadamente todas as instâncias.
class Account:
def __init__(self, balance):
self.balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError
self._balance = value@property permite acessar a lógica de um método como um atributo normal e validar leituras ou escritas. Normalmente, o valor é armazenado em outro atributo para evitar recursão infinita.
class Temperature:
def __init__(self, value):
self.value = value
@staticmethod
def is_valid(value):
return value >= -273.15
@classmethod
def zero(cls):
return cls(0)@staticmethod não recebe uma instância nem a classe, enquanto @classmethod recebe a classe por cls. Métodos de classe são úteis como construtores alternativos e preservam o comportamento das subclasses.
class Base:
def __init__(self, value):
self.value = value
class Child(Base):
def __init__(self, value, extra):
super().__init__(value)
self.extra = extraUma subclasse herda o comportamento da classe base e pode ampliá-lo ou sobrescrevê-lo. super() chama a próxima implementação na ordem de resolução de métodos, o que é especialmente importante na herança múltipla.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point(x={self.x}, y={self.y})'
def __str__(self):
return f'{self.x}, {self.y}'__repr__ é voltado para desenvolvedores e depuração, enquanto __str__ fornece uma saída amigável. Se __str__ não existir, str() usará __repr__.
class User:
def __init__(self, user_id):
self.user_id = user_id
def __eq__(self, other):
return isinstance(other, User) and self.user_id == other.user_id
def __hash__(self):
return hash(self.user_id)__eq__ define a igualdade lógica, enquanto __hash__ permite usar o objeto em conjuntos e como chave de dicionário. Objetos iguais devem ter hashes iguais, e os campos usados no hash não devem mudar.
class Item:
def __init__(self, score, name):
self.score = score
self.name = name
def __lt__(self, other):
return (self.score, self.name) < (other.score, other.name)
items = sorted([Item(2, 'b'), Item(1, 'a')])__lt__ define o operador < e permite que sorted() compare objetos diretamente. A comparação de tuplas expressa de forma prática o critério principal e os critérios de desempate.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Node:
value: int
tags: tuple[str, ...] = ()
@dataclass
class Basket:
items: list[str] = field(default_factory=list)@dataclass gera métodos como __init__ e __repr__, enquanto frozen=True impede a atribuição normal aos campos. Um valor padrão imutável é escrito diretamente, mas um mutável como [] lança ValueError na criação da classe e exige default_factory.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y__slots__ restringe os atributos disponíveis na instância e geralmente reduz o uso de memória. Sem incluir __dict__, não é possível adicionar novos atributos dinamicamente.
Anotações de tipo10
count: int = 3
def repeat(text: str, times: int) -> str:
return text * times
count = '3'As anotações descrevem os tipos esperados para analisadores estáticos, IDEs e documentação. Python não as verifica automaticamente em tempo de execução e ainda permite valores de outro tipo.
items: list[int] = [1, 2]
scores: dict[str, int] = {'a': 1}
point: tuple[int, int] = (2, 3)As coleções nativas podem ser parametrizadas com os tipos de elementos, chaves e valores. tuple[int, int] descreve uma estrutura fixa, enquanto tuple[int, ...] descreve uma tupla de tamanho variável.
from typing import Optional
left: Optional[int] = None
right: int | None = NoneOptional[int] e int | None indicam que o valor pode ser int ou None. Optional não torna um parâmetro opcional; isso exige um valor padrão.
from typing import Union
value_a: Union[int, str] = 1
value_b: int | str = 'x'Union[int, str] e int | str permitem um entre vários tipos. Uniões muito amplas dificultam o refinamento de tipos e o tratamento de todos os casos.
from typing import Any
value: Any = 1
value = 'x'
value = [1, 2]Any desativa as verificações estáticas das operações sobre um valor e é compatível com qualquer tipo nos dois sentidos. O uso excessivo esconde erros e reduz grande parte do benefício das anotações.
from collections.abc import Callable
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b)Callable[[A, B], R] descreve um objeto chamável que recebe dois argumentos e retorna R. Callable[..., R] é usado quando a lista exata de parâmetros é desconhecida.
from collections.abc import Iterable, Sequence
def total(values: Iterable[int]) -> int:
return sum(values)
def first(values: Sequence[int]) -> int:
return values[0]Iterable garante a iteração, mas não o acesso por índice nem múltiplas passagens. Sequence também fornece tamanho e indexação, sendo um contrato mais restrito.
from typing import Literal, TypeAlias
UserId: TypeAlias = int
Mode: TypeAlias = Literal['fast', 'safe']TypeAlias fornece um nome legível para outro tipo, mas não cria um tipo novo e incompatível. Literal restringe o valor a literais específicos e é útil para modos e estados.
from typing import TypedDict
class UserData(TypedDict):
name: str
age: int
user: UserData = {'name': 'a', 'age': 20}TypedDict descreve as chaves obrigatórias e os tipos de valores de um dicionário comum. Em tempo de execução, o objeto continua sendo um dict, e a estrutura é verificada apenas por ferramentas estáticas.
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
def get(self) -> T:
return self.valueTypeVar relaciona tipos em diferentes partes de uma assinatura, enquanto Generic cria uma classe parametrizada por esse tipo. Diferentemente de Any, o analisador preserva o tipo concreto em cada uso.
Testes e depuração10
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5pytest descobre funções cujo nome começa com test (padrão python_functions = test*; test_ por convenção) e trata um assert com falha como falha do teste. É melhor comparar resultados concretos para que o relatório mostre diferenças úteis.
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0pytest.raises verifica se um bloco lança o tipo de exceção esperado. O teste falha se nenhuma exceção for lançada ou se o tipo for diferente.
import pytest
@pytest.mark.parametrize('x, expected', [(1, 1), (2, 4), (3, 9)])
def test_square(x, expected):
assert x * x == expectedparametrize executa a mesma função de teste para cada conjunto de dados. Os casos permanecem independentes, então o relatório identifica a entrada exata que falhou.
import pytest
@pytest.fixture
def sample():
return [1, 2, 3]
def test_length(sample):
assert len(sample) == 3Uma fixture prepara dados ou recursos e é injetada no teste por um parâmetro com o mesmo nome. Fixtures abrangentes demais criam dependências ocultas e dificultam a compreensão do teste.
import pytest
def test_ratio():
assert 0.1 + 0.2 == pytest.approx(0.3)pytest.approx compara números usando uma tolerância absoluta ou relativa. A igualdade direta entre floats não é confiável porque muitas frações decimais não possuem representação binária exata.
import unittest
class MathTest(unittest.TestCase):
def test_add(self):
self.assertEqual(2 + 3, 5)unittest.TestCase é o modelo de testes baseado em classes da biblioteca padrão, com métodos como assertEqual, setUp e tearDown. O nome do método de teste deve começar com o prefixo test (TestLoader.testMethodPrefix) para ser descoberto pelo carregador padrão; por convenção usa-se test_.
def square(x):
"""
>>> square(3)
9
"""
return x * x
if __name__ == '__main__':
import doctest
doctest.testmod()doctest executa exemplos presentes em docstrings e compara a saída com o resultado registrado. É útil para exemplos pequenos, mas pode ser frágil quando a formatação da saída muda.
import logging
value = 3
print(value)
logging.basicConfig(level=logging.DEBUG)
logging.debug('%s', value)print é conveniente para uma verificação local rápida, enquanto logging oferece níveis, formatação e destinos configuráveis. logging geralmente é preferível em bibliotecas e aplicações de servidor.
from timeit import timeit
elapsed = timeit('sum(range(100))', number=10_000)timeit executa repetidamente um pequeno trecho e reduz o impacto de ruídos aleatórios na medição. As comparações devem usar operações equivalentes nas mesmas condições, e não apenas uma execução.
import traceback
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc()traceback.print_exc() imprime o traceback ativo dentro de um tratador de exceções. Capturar um erro sem registrá-lo ou lançá-lo novamente é perigoso porque oculta a causa original.
Entrada, saída e arquivos10
name = input().strip()`input` lê uma linha da entrada padrão, remove a quebra de linha final e sempre retorna uma `str`. Os espaços nas extremidades permanecem, a menos que `strip` seja chamado separadamente.
import sys
tokens = sys.stdin.buffer.read().split()`sys.stdin.buffer.read` lê rapidamente toda a entrada como `bytes`, e `split` a divide por qualquer espaço em branco. Essa abordagem é conveniente em problemas algorítmicos, mas mantém toda a entrada na memória.
import sys
while line := sys.stdin.readline():
line = line.rstrip("\r\n")`sys.stdin.readline` preserva a quebra de linha final e retorna uma string vazia ao chegar ao fim da entrada. A leitura linha por linha evita carregar todos os dados na memória.
import sys
print(a, b, sep=",", end="\n", file=sys.stderr)`sep` define o separador entre argumentos, `end` define o final da saída e `file` seleciona o fluxo de texto de destino. Muitas chamadas individuais a `print` podem ser mais lentas do que construir uma única string com `join`.
with open("input.txt", "r", encoding="utf-8") as f:
text = f.read()
with open("output.txt", "w", encoding="utf-8") as f:
f.write(text)`with open` garante que o arquivo seja fechado mesmo se ocorrer uma exceção, e uma codificação explícita torna o comportamento portável. O modo `w` apaga o conteúdo existente antes de gravar.
import json
data = json.loads(text)
text = json.dumps(data, ensure_ascii=False)`json.loads` interpreta uma string JSON como objetos Python, enquanto `json.dumps` realiza a conversão inversa. JSON não preserva a diferença entre listas e tuplas, e as chaves dos objetos são strings após a leitura.
import csv
with open("data.csv", newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))
with open("out.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)`csv.reader` e `csv.writer` tratam corretamente delimitadores, aspas e quebras de linha dentro dos campos. Arquivos CSV devem ser abertos com `newline=""`, e os valores lidos permanecem como strings por padrão.
from pathlib import Path
path = Path("data") / "input.txt"
text = path.read_text(encoding="utf-8")
path.write_text(text, encoding="utf-8")`pathlib.Path` cria objetos de caminho portáveis e combina suas partes com `/`. `read_text` e `write_text` são convenientes para arquivos pequenos, mas processam todo o conteúdo de uma vez.
import os
token = os.environ.get("API_TOKEN")`os.environ` expõe as variáveis de ambiente do processo como um mapeamento mutável de strings. O acesso com colchetes gera `KeyError` quando a chave não existe, enquanto `get` retorna `None` ou um valor padrão.
import sys
script, *args = sys.argv`sys.argv` contém os argumentos da linha de comando como strings, e o elemento de índice `0` normalmente contém o nome do script executado. Ele não realiza validação nem conversão; use `argparse` para uma interface estruturada.