Referência

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

Variables and types
n = 42
ratio = 1.5
name = "Ada"
flag = True
nothing = None

O tipo vem do valor; não é preciso declarar. Verifique com type(x) ou isinstance(x, int).

f-strings
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.

Truthiness
if items:
    ...
if not s:
    ...
bool(0), bool(""), bool([])   # False

Coleções vazias, 0, "" e None são falsos. Prefira `if items` a `if len(items) > 0`.

Multiple assignment
a, b = b, a
first, *rest = [1, 2, 3, 4]
x = y = 0

Troca sem variável temporária; *rest absorve o resto da sequência.

Conditional expression
sign = "+" if n >= 0 else "-"

Condicional em uma linha. Devolve um valor, então pode ser atribuída ou retornada.

Floor division and modulo
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.

Length with len
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()`.

Integer input
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.

Type conversion and integer base
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`.

Type checks with isinstance
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.

None as a default value
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`.

Identity versus equality
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 / elif / else
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.

Comparisons and chains
a == b
a != b
a < b
a <= b
a is None
0 <= x < n
a < b == c
x not in blocked

Uma 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.

Boolean operators
value = cached or compute()
result = ready and data
flag = not items

and 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 and while loops
for item in items:
    process(item)

while left < right:
    left += 1

for 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
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.

Break
for x in items:
    if x == target:
        break

break encerra imediatamente o laço mais próximo. Em laços aninhados, o laço externo continua.

Continue
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.

Loop else
for x in items:
    if x == target:
        found = True
        break
else:
    found = False

O 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 / case
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.

Unpacking in loops
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

Function and multiple return values
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.

Default arguments
def power(base, exponent=2):
    return base ** exponent

O valor padrão é usado quando o argumento é omitido. A expressão padrão é avaliada uma única vez quando a função é criada.

Mutable default argument
def add_item(item, items=[]):
    items.append(item)
    return items

Um 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.

Variadic arguments
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.

Keyword-only arguments
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.

Lambda
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.

Closure
def make_adder(delta):
    def add(value):
        return value + delta
    return add

Um 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.

Global and nonlocal
count = 0

def outer():
    total = 0

    def update():
        global count
        nonlocal total
        count += 1
        total += 1
        return total

    return update

global 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.

LRU cache
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.

Type annotations
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

String slicing
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.

split / join
"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.

String methods
s.strip()
s.lower()
s.upper()
s.replace("a", "b")
s.startswith("ab")
s.endswith(".py")
s.count("a")
s.find("a")   # -1

Strings são imutáveis: os métodos retornam outra. find devolve -1 se não achar; index levanta erro.

Character checks and membership
s.isdigit()
s.isalpha()
s.isalnum()
c.isupper()
c.islower()
"ABC123".isupper()   # True
"123".isupper()      # False
"ab" in s

isdigit/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.

Comparing strings by characters
sorted(s)
sorted(s1) == sorted(s2)
set("aab") == set("ab")        # True
sorted("aab") == sorted("ab")  # False

sorted(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.

Building a string
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 / chr
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

Formatted strings
"{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 `}}`.

String alignment and filling
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.

Character translation table
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.

String partition
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.

Split text into lines
"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.

Zero-filled string
"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.

Remove exact prefix or suffix
"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.

Regular expression basics
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`.

Text encoding and decoding
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.

Unicode case folding
"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

List slicing
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.

append / extend / insert / pop
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.

enumerate
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.

zip
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.

Comprehensions
[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 / all / sum / min / max
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.

Building a 2D grid
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.

List creation and repetition
a = [1, 2, 3]
b = list(range(3))
zeros = [0] * 5
rows = [[0] * 3] * 2

Um 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.

List index and count
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.

List element removal
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.

Shallow and deep list copies
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.

List argument unpacking
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`.

Tuple as an immutable sequence
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

Dict access and defaults
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.

Iterating a dict
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.

Counter
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.

defaultdict
from collections import defaultdict
g = defaultdict(list)
g[k].append(x)
cnt = defaultdict(int)
cnt[k] += 1

O valor é criado no primeiro acesso. Bom para agrupar e para listas de adjacência de grafos.

Sets
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_set

Pertencimento é O(1) contra O(n) da lista. & é interseção, | união, - diferença. Um set não guarda ordem nem duplicatas.

The "seen" set pattern
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.

Dictionary creation
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.

Dictionary membership and removal
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.

Dictionary views
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`.

Dictionary merging
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.

Dictionary sorting by value
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.

Dictionary frequency counter
freq = {}
for x in [2, 1, 2]:
    freq[x] = freq.get(x, 0) + 1

As 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.

Frozenset as a dictionary key
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

Deque ends
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.

Bounded deque and rotation
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.

Min heap
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).

Heap selection and max heap
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.

Counter operations
from collections import Counter
a = Counter("banana")
a.update("band")
top = a.most_common(2)
b = Counter("an")
added = a + b
common = a & b

Counter 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.

Default dictionary
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.

Ordered dictionary
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.

Named tuple
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(2, 3)
x = p.x

namedtuple 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.

Array and bytes
from array import array
nums = array("i", [1, 2, 3])
raw = bytes([65, 66, 67])
mutable = bytearray(raw)
mutable[0] = 90

array 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.

Chain map
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

Generator expression and list comprehension
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.

Yield
def countdown(n):
    while n > 0:
        yield n
        n -= 1

O 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.

Yield from
def flatten(chunks):
    for chunk in chunks:
        yield from chunk

yield from repassa os itens de um iterável aninhado sem exigir um laço interno manual. Cada uso achata apenas um nível.

Iter, next, and StopIteration
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.

Count, cycle, and islice
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.

Chain
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.

Product, permutations, and combinations
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.

Groupby
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.

Reversed
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.

Generator aggregation
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

Arbitrary-precision integers
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.

Floating-point precision
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.

Bankers rounding
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.

Absolute value and powers
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.

Math roots and number theory
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.

Floor, ceiling and infinity
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.

Division by 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.

Numeric conversions and bases
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.

Bitwise operations
x = 0b1100
y = 0b1010
x & y           # 8
x | y           # 14
x ^ y           # 6
~x              # -13
x << 2          # 48
x >> 2          # 3
x.bit_count()   # 2

As 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`.

Pseudo-random values
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

sorted / list.sort
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.

Sort key
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.

Stable sort
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.

itemgetter
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.

bisect
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.

Binary search by hand
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 -1

A lista precisa estar ordenada. Mova os limites além de mid, senão o laço não termina.

Pilha, fila e dois ponteiros9

List as stack
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).

Deque as queue
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.

Monotonic stack
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 result

Uma 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.

Balanced brackets
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 stack

Os 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.

Opposite pointers
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 False

Ponteiros 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.

Fast and slow pointers
def middle_node(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow

Um 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.

Remove duplicates in place
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 write

Um 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.

Reverse in place
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 -= 1

Trocar 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.

Merge sorted sequences
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 result

Dois 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

Fixed sliding window
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 result

Uma 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.

Variable sliding window
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 answer

Uma 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.

Window frequency counter
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 result

As 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.

Maximum window sum
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 best

A 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.

Prefix sums
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.

Adjacent differences
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.

Running accumulation
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.

Target subarray sum
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 False

Um 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.

Binary search on answer
def first_true(low, high, feasible):
    while low < high:
        mid = (low + high) // 2
        if feasible(mid):
            high = mid
        else:
            low = mid + 1
    return low

A 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

Recursive base and step
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.

Recursion depth limit
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.

Tail recursion
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.

Memoization decorators
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.

Bottom-up dynamic programming
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.

Two-dimensional dynamic programming
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.

DP answer reconstruction
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 answer

Alé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.

Divide and conquer
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.

Backtracking permutations
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 result

O backtracking escolhe uma opção, continua recursivamente e depois restaura o estado alterado. Gerar todas as permutações exige tempo fatorial.

Backtracking subsets
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 result

Para 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

Adjacency list
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.

Shortest path in an unweighted graph
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.

Tree depth-first traversals
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.

Tree level-order traversal
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 levels

A 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.

Directed cycle detection
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.

Topological sort
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 None

O 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.

Disjoint set union
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 True

Union-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, except, else, finally
try:
    value = int(text)
except ValueError:
    value = 0
else:
    value += 1
finally:
    closed = True

except é 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.

Catch a specific exception
try:
    item = data[key]
except KeyError:
    item = None

Capture 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.

Catch multiple exception types
try:
    result = values[index] / divisor
except (IndexError, ZeroDivisionError):
    result = None

Vá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.

Raise and re-raise
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:
        raise

raise 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.

Custom exception
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.

Common built-in exceptions
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.

Assert statement
def divide(a, b):
    assert b != 0
    return a / b

assert 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 context manager
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.

Suppress exceptions
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.

EAFP and LBYL
def eafp(data, key):
    try:
        return data[key]
    except KeyError:
        return None

def lbyl(data, key):
    if key in data:
        return data[key]
    return None

EAFP 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, initializer, and instance method
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def move(self, dx, dy):
        self.x += dx
        self.y += dy

Uma classe descreve a estrutura dos objetos, enquanto __init__ inicializa cada nova instância. self é passado automaticamente e representa o objeto atual.

Instance and class attributes
class Counter:
    total = 0

    def __init__(self):
        self.value = 0
        Counter.total += 1

Um 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.

Property
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.

Static and class methods
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.

Inheritance and super
class Base:
    def __init__(self, value):
        self.value = value

class Child(Base):
    def __init__(self, value, extra):
        super().__init__(value)
        self.extra = extra

Uma 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.

String representations
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__.

Equality and hashing
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.

Object ordering with __lt__
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.

Frozen dataclass with defaults
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.

Slots
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

Variable and function annotations
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.

Collection annotations
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.

Optional values
from typing import Optional

left: Optional[int] = None
right: int | None = None

Optional[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.

Union types
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.

Any
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.

Callable
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.

Iterable and Sequence
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.

Type aliases and Literal
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.

TypedDict
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.

Generic and TypeVar
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.value

TypeVar 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

Pytest test and assert
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5

pytest 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.

Expected exception with pytest.raises
import pytest

def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

pytest.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.

Parameterized test
import pytest

@pytest.mark.parametrize('x, expected', [(1, 1), (2, 4), (3, 9)])
def test_square(x, expected):
    assert x * x == expected

parametrize 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.

Pytest fixture
import pytest

@pytest.fixture
def sample():
    return [1, 2, 3]

def test_length(sample):
    assert len(sample) == 3

Uma 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.

Approximate numeric comparison
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.

Unittest TestCase
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_.

Doctest
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.

Benchmarking with timeit
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.

Printing a traceback
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

Read one input line
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.

Read all standard input
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.

Read standard input line by line
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.

Configure printed output
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`.

Read and write text files
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.

JSON serialization
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.

CSV reading and writing
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.

Path objects
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.

Environment variables
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.

Command-line arguments
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.