Primeiros Passos
Hello World & Comentários
Python usa # para comentários e aspas triplas para docstrings. A função print() suporta parâmetros sep e end para personalizar a formatação da saída. Docstrings servem como documentação acessível via help() e __doc__.
# This is a single-line comment
"""
This is a
multi-line comment (docstring)
"""
print("Hello, World!") # print to stdout
print("A", "B", "C", sep="-") # A-B-C
print("No newline", end="") # suppress newlineIndentação & Blocos de Código
Diferente da maioria das linguagens, Python usa indentação em vez de chaves para definir blocos de código. A consistência é crítica — misturar tabs e espaços causa SyntaxError. O PEP 8 recomenda 4 espaços por nível.
# Python uses indentation (4 spaces) to define blocks
if True:
print("inside if")
if True:
print("nested block")
print("outside block")
# No braces! Indentation IS the syntax
def func():
x = 1
return x + 1Entrada & Saída
input() lê de stdin como uma string — sempre converta quando precisar de um número. Use int(), float(), etc. para conversão. F-strings (Python 3.6+) são a forma preferida de formatar strings.
# input() always returns a string
name = input("Enter your name: ")
age = int(input("Enter your age: ")) # convert to int
print(f"Hello {name}, you are {age} years old")
# formatted output
print("Pi is approximately {:.2f}".format(3.14159))
print(f"{1000000:,}") # 1,000,000 with thousands separatorMúltiplas Instruções & Continuação de Linha
Use ponto e vírgula para separar instruções em uma linha (raro em Python idiomático). Linhas longas podem ser continuadas com barra invertida, ou automaticamente dentro de (), [], {}. Prefira continuação implícita para legibilidade.
# multiple statements on one line (discouraged)
a = 1; b = 2; c = 3
# explicit line continuation
total = 1 + 2 + 3 + \
4 + 5 + 6
# implicit continuation inside brackets
nums = [
1, 2, 3,
4, 5, 6
]
result = (1 + 2
+ 3 + 4)Execução do Python
Scripts Python rodam com 'python script.py'. O REPL permite experimentação interativa. Sempre use python3 em sistemas onde python aponta para Python 2. A linha shebang torna scripts executáveis no Unix.
# Run a script
# $ python script.py
# Run interactively (REPL)
# $ python
# >>> 2 + 2
# 4
# Shebang line for Unix scripts
#!/usr/bin/env python3
# Check Python version
import sys
print(sys.version)
print(sys.version_info.major) # 3Variáveis & Tipos de Dados
Variáveis & Tipagem Dinâmica
Python usa tipagem dinâmica — variáveis podem mudar de tipo em tempo de execução. Use type() para verificar, isinstance() para confirmar. Python 3.6+ suporta type hints (name: str = 'Alice') para suporte de IDE sem aplicação em tempo de execução.
# Python is dynamically typed - no declaration needed
name = "Alice" # str
age = 30 # int
height = 5.7 # float
is_active = True # bool
items = [1, 2, 3] # list
# Type checking
print(type(name)) # <class 'str'>
print(isinstance(age, int)) # True
# Multiple assignment
x, y, z = 1, 2, 3
a = b = 0 # chain assignmentType Hints (Python 3.6+)
Type hints melhoram a legibilidade do código e habilitam autocompletar em IDEs e análise estática com mypy. Eles NÃO são aplicados em tempo de execução — Python permanece dinamicamente tipado. Use Optional[X] para valores que podem ser None.
# Variable annotations
name: str = "Alice"
age: int = 30
scores: list[float] = [90.5, 85.0]
# Function annotations
def greet(name: str, times: int = 1) -> str:
return (f"Hi {name}! " * times).strip()
# Optional and Union
from typing import Optional, Union
def find(id: int) -> Optional[str]:
return "Alice" if id == 1 else None
# mypy for static type checking
# $ mypy script.pyConversão de Tipos
Python tem funções de conversão integradas: int(), float(), str(), bool(), list(), tuple(), set(), dict(). Valores falsy incluem 0, '', [], {}, None, False. int() trunca em direção a zero, enquanto round() usa arredondamento bancário.
# String to number
num_str = str(42) # "42"
num = int("42") # 42
float_num = float("3.14") # 3.14
# Number conversions
print(int(3.99)) # 3 (truncates toward zero)
print(int(-3.99)) # -3
print(round(3.14159, 2)) # 3.14
# Boolean conversion
print(bool(0)) # False
print(bool("")) # False
print(bool([])) # False
print(bool("anything")) # True
# Collection conversions
print(list("abc")) # ['a', 'b', 'c']
print(tuple([1, 2, 3])) # (1, 2, 3)
print(set([1, 1, 2])) # {1, 2}Tipos Numéricos
Ints do Python têm precisão arbitrária (sem overflow). Floats são doubles IEEE 754 com os problemas de precisão usuais. Números complexos são integrados. Booleanos são uma subclasse de int (True==1, False==0). Use underscores em literais numéricos para legibilidade.
# Integers (arbitrary precision)
big = 10 ** 100 # no overflow
print(type(big)) # <class 'int'>
# Floats (IEEE 754 double)
pi = 3.14159
print(0.1 + 0.2) # 0.30000000000000004
# Complex numbers
z = 3 + 4j
print(z.real, z.imag) # 3.0 4.0
print(abs(z)) # 5.0
# Boolean is subclass of int
print(isinstance(True, int)) # True
print(True + True) # 2
# Underscores in numbers (3.6+)
million = 1_000_000
binary = 0b_1010_1010Constantes & Convenções de Nomenclatura
Python não tem palavra-chave const — nomes ALL_CAPS são constantes apenas por convenção (nada impede reatribuição). O PEP 8 define nomenclatura: snake_case para variáveis/funções, PascalCase para classes, ALL_CAPS para constantes. Underscore inicial significa 'privado' por convenção; underscore duplo aciona name mangling.
# Python has no true constants - convention only
MAX_SIZE = 100 # ALL_CAPS for constants
PI = 3.14159
# Naming conventions (PEP 8)
variable_name = "snake_case" # variables, functions
ClassName = "PascalCase" # classes
CONSTANT_VALUE = 100 # constants
_private_var = "underscore prefix" # private (convention)
__name_mangled = "double underscore" # name mangling
# dunder names (reserved)
__name__, __main__, __init__Strings
Métodos de String
Strings são imutáveis — métodos retornam novas strings. find() retorna -1 se não encontrado, enquanto index() levanta ValueError. Use isalpha()/isdigit()/isalnum() para validação. A classe str tem mais de 40 métodos — explore com dir(str).
s = "Hello, World"
# Case operations
print(s.upper()) # HELLO, WORLD
print(s.lower()) # hello, world
print(s.title()) # Hello, World
print(s.capitalize()) # Hello, world
print(s.swapcase()) # hELLO, wORLD
# Search & replace
print(s.find("World")) # 7 (index, -1 if not found)
print(s.index("World")) # 7 (raises ValueError if not found)
print(s.replace("o", "0")) # Hell0, W0rld
print(s.count("l")) # 3
# Validation
print("abc".isalpha()) # True
print("123".isdigit()) # True
print(" ".isspace()) # TrueFormatação de Strings
F-strings são a forma moderna, mais rápida e legível de formatar strings. Elas suportam especificações de formato após dois pontos: :.2f para 2 decimais, :>10 para alinhar à direita com largura 10, :, para separador de milhares. Evite %-formatação em código novo.
name = "Alice"
age = 30
# f-strings (Python 3.6+) - PREFERRED
print(f"Hello, {name}! You are {age}.")
print(f"{name.upper()} is {age * 365} days old")
print(f"{3.14159:.2f}") # 3.14
print(f"{42:>10}") # right-align
print(f"{42:<10}") # left-align
print(f"{42:^10}") # center
print(f"{1000000:,}") # 1,000,000
# str.format() method
print("Hello, {}!".format(name))
print("{name} is {age}".format(name="Bob", age=25))
# Old style (avoid in new code)
print("Hello, %s!" % name)Slicing & Indexação
A sintaxe de slicing do Python [start:stop:step] é poderosa — stop é exclusivo. Índices negativos contam a partir do final. s[::-1] é a forma idiomática de reverter uma string. Slicing é seguro: índices fora do intervalo retornam strings vazias em vez de levantar erros.
s = "Hello, World"
# Indexing (0-based, negative from end)
print(s[0]) # H
print(s[-1]) # d
print(s[7]) # W
# Slicing [start:stop:step]
print(s[0:5]) # Hello
print(s[7:]) # World
print(s[:5]) # Hello
print(s[::2]) # HloWrd (every 2nd char)
print(s[::-1]) # dlroW ,olleH (reverse!)
# Length
print(len(s)) # 12
# Slicing never raises IndexError
print(s[100:200]) # '' (empty string)Divisão & Junção
split() divide uma string em uma lista, join() combina uma lista em uma string. Sempre use join() para concatenação eficiente de muitas strings — o operador + cria strings intermediárias. partition() divide em exatamente 3 partes (antes, separador, depois).
# Split
csv = "a,b,c,d"
print(csv.split(",")) # ['a', 'b', 'c', 'd']
print(csv.split(",", 2)) # ['a', 'b', 'c,d'] (max 2 splits)
# Splitlines
text = "line1\nline2\nline3"
print(text.splitlines()) # ['line1', 'line2', 'line3']
# Partition (splits on first occurrence)
print("[email protected]".partition("@"))
# ('user', '@', 'domain.com')
# Join
words = ["Hello", "World"]
print(" ".join(words)) # Hello World
print("-".join(["2024", "01", "15"])) # 2024-01-15
print("".join(["a", "b", "c"])) # abc
# String concatenation
s = "Hello" + " " + "World"
parts = ["a"]
parts += "b" # NOT string concat - adds chars to list!Remoção & Preenchimento
strip() remove espaços em branco iniciais/finais por padrão, ou caracteres especificados. zfill() preenche com zeros à esquerda (útil para IDs). rjust/ljust/center preenchem para uma largura especificada com um caractere de preenchimento opcional.
# Strip whitespace (or specified chars)
s = " hello "
print(s.strip()) # "hello"
print(s.lstrip()) # "hello "
print(s.rstrip()) # " hello"
# Strip specific characters
print("xxxhelloxxx".strip("x")) # hello
# Padding / centering
print("42".zfill(5)) # 00042
print("hi".rjust(10)) # " hi"
print("hi".ljust(10, "-")) # "hi--------"
print("hi".center(10, "*")) # "****hi****"
# expandtabs
print("a\tb".expandtabs(4)) # "a b"Raw Strings & Escapes
Raw strings (r'...') tratam barras invertidas literalmente — essencial para padrões regex e caminhos de arquivo do Windows. Strings com aspas triplas preservam novas linhas. Strings suportam o operador * para repetição e + para concatenação.
# Escape sequences
print("Line1\nLine2") # newline
print("Tab\there") # tab
print("Quote: \"hi\"") # escaped quotes
print("Backslash: \\") # literal backslash
# Raw strings (ignore escapes) - great for regex
path = r"C:\Users\name\file.txt"
regex = r"\d{3}-\d{4}"
print(path) # C:\Users\name\file.txt
# Triple-quoted strings
multi = """
Multiple
lines
"""
# String multiplication
print("ab" * 3) # abababNúmeros & Matemática
Operadores Aritméticos
Python tem 7 operadores aritméticos. / sempre retorna float, // é divisão de piso (arredonda em direção ao infinito negativo). ** é exponenciação (não ^ — isso é XOR). O resultado do operador % assume o sinal do divisor, diferente de C/Java.
# Basic operators
print(7 + 3) # 10 addition
print(7 - 3) # 4 subtraction
print(7 * 3) # 21 multiplication
print(7 / 3) # 2.333... true division (always float)
print(7 // 3) # 2 floor division
print(7 % 3) # 1 modulo (remainder)
print(7 ** 3) # 343 exponentiation
# Floor division with negatives
print(-7 // 3) # -3 (rounds toward negative infinity)
print(-7 % 3) # 2 (result has same sign as divisor)
# Augmented assignment
x = 10
x += 5 # x = x + 5
x **= 2 # x = x ** 2Módulo Math
O módulo math fornece funções e constantes matemáticas. Todas as funções trigonométricas usam radianos — converta com math.radians()/degrees(). math.gcd() encontra o máximo divisor comum. Para números complexos, use o módulo cmath.
import math
# Constants
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045
print(math.inf) # inf
print(math.nan) # nan
# Functions
print(math.sqrt(16)) # 4.0
print(math.pow(2, 10)) # 1024.0
print(math.log(100, 10)) # 2.0 (log base 10)
print(math.log(math.e)) # 1.0 (natural log)
print(math.factorial(5)) # 120
print(math.gcd(12, 8)) # 4
# Rounding
print(math.floor(3.7)) # 3
print(math.ceil(3.2)) # 4
print(math.trunc(-3.7)) # -3 (toward zero)
# Trigonometry (radians)
print(math.sin(math.pi / 2)) # 1.0
print(math.degrees(math.pi)) # 180.0Módulo Random
O módulo random usa o PRNG Mersenne Twister — NÃO é criptograficamente seguro. Use o módulo secrets para segurança. random.sample() escolhe itens únicos, random.choices() permite duplicatas. Defina uma seed para resultados reproduzíveis em testes.
import random
# Random integers
print(random.randint(1, 100)) # 1 to 100 inclusive
print(random.randrange(0, 10, 2)) # even number 0,2,4,6,8
# Random floats
print(random.random()) # 0.0 to 1.0
print(random.uniform(1.0, 10.0)) # random float in range
# Choice & sampling
colors = ["red", "green", "blue"]
print(random.choice(colors)) # one random item
print(random.sample(colors, 2)) # 2 unique items
print(random.choices(colors, k=5)) # 5 items (with replacement)
# Shuffle (in-place)
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)
print(nums)
# Reproducible randomness
random.seed(42) # same seed = same sequenceDecimal & Frações
Use Decimal para cálculos financeiros onde erros de precisão de float são inaceitáveis (ex.: dinheiro). Use Fraction para aritmética racional exata. Ambos são mais lentos que float, mas evitam erros de arredondamento. Sempre construa Decimal a partir de strings, não floats.
from decimal import Decimal, getcontext
from fractions import Fraction
# Float precision issues
print(0.1 + 0.2) # 0.30000000000000004
# Decimal for exact decimal arithmetic
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b) # 0.3 (exact!)
# Set precision
getcontext().prec = 6
print(Decimal(1) / Decimal(7)) # 0.142857
# Fractions for exact rational arithmetic
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1 + f2) # 1/2
print(float(f1)) # 0.3333...
# Fraction from string
print(Fraction("3/4")) # 3/4Operadores Bit a Bit
Operadores bit a bit manipulam bits individuais de inteiros. Inteiros do Python têm precisão arbitrária, então shifts funcionam diferente de linguagens de largura fixa. Usos comuns: flags, máscaras, análise de protocolo de baixo nível. x & (x-1) == 0 verifica se x é potência de 2.
# Bitwise operators work on integers
a = 0b1010 # 10
b = 0b1100 # 12
print(a & b) # 8 (0b1000) AND
print(a | b) # 14 (0b1110) OR
print(a ^ b) # 6 (0b0110) XOR
print(~a) # -11 (NOT, two's complement)
print(a << 2) # 40 (left shift, multiply by 4)
print(a >> 1) # 5 (right shift, divide by 2)
# Binary representation
print(bin(10)) # 0b1010
print(hex(255)) # 0xff
print(oct(8)) # 0o10
print(int("1010", 2)) # 10 (parse binary)
# Common tricks
print(5 & 1) # 1 (check odd: nonzero = odd)
print(8 & (8-1)) # 0 (check power of 2)Estruturas de Dados
Listas
Listas são a estrutura de dados mais versátil do Python — ordenadas, mutáveis e heterogêneas. append() é O(1), insert(0, x) é O(n). Use collections.deque para operações rápidas em ambas as extremidades. sort() é in-place, sorted() retorna uma nova lista.
# Lists are ordered, mutable sequences
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14]
# Adding elements
nums.append(6) # [1,2,3,4,5,6]
nums.insert(0, 0) # [0,1,2,3,4,5,6]
nums.extend([7, 8]) # extend with another list
# Removing elements
nums.remove(0) # remove by value
popped = nums.pop() # remove & return last
popped = nums.pop(0) # remove & return by index
del nums[0] # delete by index
nums.clear() # remove all
# Slicing (same as strings)
nums = [1, 2, 3, 4, 5]
print(nums[1:3]) # [2, 3]
print(nums[::-1]) # [5, 4, 3, 2, 1] reverse
# Sorting
nums.sort() # in-place sort
nums.sort(reverse=True) # descending
sorted_nums = sorted(nums) # returns new listTuplas
Tuplas são imutáveis e mais rápidas que listas. Use-as para coleções fixas, múltiplos valores de retorno e chaves de dicionário (listas não podem ser chaves). Named tuples fornecem nomes de campos para legibilidade. Tuplas de elemento único precisam de vírgula à direita.
# Tuples are ordered, IMMUTABLE sequences
point = (3, 4)
single = (42,) # note the comma for single-element tuple
empty = ()
# Packing & unpacking
coordinates = 10, 20, 30 # packing
x, y, z = coordinates # unpacking
x, y = y, x # swap values!
# Multiple return values
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 4, 1, 5])
# Named tuples (readable)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4
# Tuples are immutable but can contain mutable objects
t = (1, [2, 3])
t[1].append(4) # OK: (1, [2, 3, 4])Dicionários
Dicionários são hash maps — O(1) em média para busca/inserção/exclusão. Chaves devem ser hashable (imutáveis). Desde Python 3.7, dicts mantêm a ordem de inserção. Use get() para evitar KeyError. dict comprehension cria dicts de forma elegante.
# Dicts are key-value mappings (insertion-ordered since 3.7)
user = {"name": "Alice", "age": 30}
# Access
print(user["name"]) # Alice
print(user.get("email")) # None (no KeyError)
print(user.get("email", "N/A")) # N/A (default)
# Add/update
user["email"] = "[email protected]" # add
user["age"] = 31 # update
user.setdefault("role", "user") # set if missing
# Delete
del user["email"]
val = user.pop("age") # remove & return
# user.clear() # remove all
# Iteration
for key in user: # keys
print(key)
for k, v in user.items(): # key-value pairs
print(k, v)
for v in user.values(): # values
print(v)
# Dict comprehension
squares = {x: x**2 for x in range(5)}
# Merge dicts (3.9+)
merged = {"a": 1} | {"b": 2}Conjuntos (Sets)
Sets são coleções não ordenadas de elementos únicos e hashable. Eles se destacam em teste de pertinência (O(1) vs O(n) para listas) e álgebra de conjuntos (união, interseção, diferença). frozenset é imutável e hashable. A ordem não é garantida.
# Sets are unordered collections of unique elements
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# Set operations
print(a | b) # union: {1, 2, 3, 4, 5, 6}
print(a & b) # intersection: {3, 4}
print(a - b) # difference: {1, 2}
print(a ^ b) # symmetric difference: {1, 2, 5, 6}
# Methods
a.add(5) # add element
a.discard(10) # remove if present (no error)
a.remove(1) # remove (KeyError if missing)
a.update([6, 7]) # add multiple
# Membership test (O(1) - faster than list)
print(3 in a) # True
# Frozen set (immutable)
fs = frozenset([1, 2, 3])
# Common use: deduplicate
unique = list(set([1, 1, 2, 2, 3])) # [1, 2, 3]Comprehensions
Comprehensions são uma forma pythônica de criar coleções de forma concisa. Expressões geradoras (parênteses em vez de colchetes) são preguiçosas — produzem valores sob demanda, economizando memória. Prefira comprehensions a map()/filter() para legibilidade.
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(3)]
# Dict comprehension
square_map = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Set comprehension
unique_lens = {len(w) for w in ["a", "ab", "abc", "ab"]}
# Generator expression (lazy, memory-efficient)
gen = (x**2 for x in range(1000000))
print(next(gen)) # 0
print(next(gen)) # 1
total = sum(x**2 for x in range(100)) # no extra list
# Nested comprehension (matrix)
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]Módulo Collections
O módulo collections fornece contêineres especializados. Counter conta itens hashable. defaultdict cria automaticamente chaves ausentes. deque oferece append/pop O(1) em ambas as extremidades (vs O(n) para listas). Esses são essenciais para código limpo e eficiente.
from collections import Counter, defaultdict, deque, OrderedDict
# Counter - counting
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
cnt = Counter(words)
print(cnt) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(cnt.most_common(2)) # [('apple', 3), ('banana', 2)]
# defaultdict - no KeyError
dd = defaultdict(list)
dd["fruits"].append("apple")
dd["fruits"].append("banana")
# dd["vegs"] automatically creates empty list
# deque - fast double-ended queue
dq = deque([1, 2, 3])
dq.appendleft(0) # [0, 1, 2, 3]
dq.append(4) # [0, 1, 2, 3, 4]
dq.popleft() # 0, deque is now [1, 2, 3, 4]
dq.rotate(1) # rotate right
# OrderedDict (less needed since 3.7, dicts are ordered)
od = OrderedDict([("a", 1), ("b", 2)])Fluxo de Controle
If / Elif / Else
Python usa if/elif/else — note 'elif' não 'elseif'. Indentação define blocos. O ternário 'x if cond else y' é uma expressão. Python trata coleções vazias, 0, None e False como falsy — útil para condicionais concisas.
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(f"Grade: {grade}") # Grade: B
# Conditional expression (ternary)
status = "pass" if score >= 60 else "fail"
# Truthy/falsy values
# Falsy: False, 0, 0.0, "", [], {}, (), None
# Everything else is truthy
if []: # False
print("never")
if [0]: # True (non-empty list)
print("always")Loops For & Iteração
O loop for do Python itera sobre qualquer iterável. range() gera números (stop exclusivo). enumerate() pareia itens com índices. zip() itera múltiplas sequências em paralelo. Use .items() para iterar pares chave-valor de dict.
# range(start, stop, step)
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8
print(i)
for i in range(10, 0, -1): # countdown
print(i)
# Iterate over collections
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# enumerate for index + value
for idx, fruit in enumerate(fruits):
print(f"{idx}: {fruit}")
# zip to iterate multiple sequences
names = ["Alice", "Bob"]
ages = [30, 25]
for name, age in zip(names, ages):
print(f"{name}: {age}")
# Iterate dict
user = {"name": "Alice", "age": 30}
for key, value in user.items():
print(f"{key} = {value}")Loops While & Break/Continue
loops while repetem enquanto uma condição for verdadeira. break sai do loop imediatamente, continue pula para a próxima iteração. A construção for/else executa o bloco else apenas se o loop não foi interrompido. pass é um placeholder no-op para blocos vazios.
# Basic while
count = 0
while count < 5:
print(count)
count += 1
# break - exit loop
while True:
cmd = input("> ")
if cmd == "quit":
break
print(f"You said: {cmd}")
# continue - skip to next iteration
for i in range(10):
if i % 2 == 0:
continue # skip even numbers
print(i) # prints 1, 3, 5, 7, 9
# else clause (runs if no break)
for i in range(5):
if i == 10:
break
else:
print("Loop completed without break")
# pass - do nothing (placeholder)
for i in range(5):
pass # TODO: implementMatch Statement (Python 3.10+)
O match statement (Python 3.10+) é um pattern matching estrutural poderoso, muito além do switch do C. Pode corresponder sequências, mapeamentos, instâncias de classes e vincular variáveis. O padrão _ é um curinga (default). Guards com 'if' adicionam condições.
# Structural pattern matching (like switch)
def handle_command(cmd):
match cmd.split():
case ["quit"]:
return "Goodbye"
case ["hello", name]:
return f"Hello, {name}!"
case ["move", direction] if direction in "NSEW":
return f"Moving {direction}"
case ["add", x, y]:
return int(x) + int(y)
case _:
return "Unknown command"
print(handle_command("hello Alice")) # Hello, Alice!
# Matching data structures
match point:
case (0, 0):
print("origin")
case (0, y):
print(f"on y-axis at {y}")
case (x, 0):
print(f"on x-axis at {x}")
case (x, y):
print(f"at ({x}, {y})")Iteradores & Geradores
Geradores produzem valores de forma preguiçosa usando yield — eles não computam todos os valores antecipadamente, economizando memória. Eles implementam o protocolo iterador (iter() e next()). Uma vez esgotados, estão concluídos. Use geradores para sequências grandes/infinitas, pipelines e streaming de dados.
# Iterator protocol
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) # StopIteration
# Generator function (uses yield)
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
for num in count_up_to(5):
print(num) # 1, 2, 3, 4, 5
# Infinite generator
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
print(next(fib)) # 0
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2
# Generator expression
squares = (x**2 for x in range(10))Funções
Definindo & Chamando Funções
Funções são definidas com def. Argumentos padrão usam =. Python suporta argumentos nomeados para clareza. Funções podem retornar múltiplos valores (como uma tupla). Docstrings (aspas triplas) documentam funções e são acessíveis via help().
# Basic function
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!
# Default arguments
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Bob")) # Hello, Bob!
print(greet("Bob", "Hi")) # Hi, Bob!
# Keyword arguments
print(greet(name="Carol", greeting="Hey"))
# Return multiple values (tuple)
def stats(nums):
return min(nums), max(nums), sum(nums) / len(nums)
lo, hi, avg = stats([1, 2, 3, 4, 5])
# Docstrings
def add(a, b):
"""Add two numbers and return the result.
Args:
a: First number
b: Second number
Returns:
Sum of a and b
"""
return a + bArgumentos: *args & **kwargs
*args coleta argumentos posicionais extras em uma tupla, **kwargs coleta argumentos nomeados extras em um dict. Os nomes args/kwargs são convenção. Você pode descompactar sequências com * e dicts com ** ao chamar funções. Ordem: posicional, *args, nomeado, **kwargs.
# *args - variable positional arguments (tuple)
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs - variable keyword arguments (dict)
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, role="admin")
# Combining all
def func(a, b, *args, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")
func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2, args=(3, 4), kwargs={'x': 5, 'y': 6}
# Unpacking arguments
nums = [1, 2, 3]
print(sum_all(*nums)) # unpack list as args
opts = {"name": "Alice", "age": 30}
print_info(**opts) # unpack dict as kwargsLambda & Funções de Ordem Superior
Lambdas são limitadas a uma única expressão — use def para lógica complexa. Elas brilham como argumentos para funções de ordem superior como sorted(), map(), filter(). No entanto, list comprehensions são frequentemente mais legíveis que map/filter. reduce() vive em functools.
# Lambda - anonymous function (single expression)
square = lambda x: x ** 2
print(square(5)) # 25
# Common with sorted, map, filter, reduce
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
# Sort by score (key function)
sorted_by_score = sorted(students, key=lambda s: s[1])
# [('Carol', 78), ('Alice', 85), ('Bob', 92)]
# map - apply function to each item
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8, 10]
# filter - keep items where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]
# reduce - accumulate to single value
from functools import reduce
product = reduce(lambda a, b: a * b, nums)
# 120 (1*2*3*4*5)
# Prefer comprehensions over map/filter
doubled = [x * 2 for x in nums] # more Pythonic
evens = [x for x in nums if x % 2 == 0]Decoradores
Decoradores envolvem funções para adicionar comportamento sem modificar o código original. A @syntax é açúcar sintático. Decoradores com argumentos precisam de um nível extra de aninhamento. Sempre use functools.wraps para preservar os metadados da função original (nome, docstring).
# A decorator modifies a function's behavior
def uppercase_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_result
def greet(name):
return f"hello, {name}"
print(greet("alice")) # HELLO, ALICE
# Decorator with arguments
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi() # prints "Hi!" three times
# Practical: timing decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
# Use functools.wraps to preserve metadata
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperEscopo & Closures
Python resolve nomes usando a ordem de escopo LEGB: Local, Enclosing, Global, Built-in. Use 'global' para reatribuir uma variável global dentro de uma função. Use 'nonlocal' (Python 3) para modificar uma variável em um escopo envolvente. Closures lembram de seu escopo envolvente.
# LEGB rule: Local, Enclosing, Global, Built-in
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
# global keyword - modify global variable
count = 0
def increment():
global count
count += 1
# nonlocal keyword - modify enclosing variable
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3OOP & Classes
Classes & Objetos
Classes agrupam dados (atributos) e comportamento (métodos). __init__ é o construtor. self refere-se à instância (como 'this' em outras linguagens). Variáveis de classe são compartilhadas; variáveis de instância são por objeto. __str__ é para usuários, __repr__ é para desenvolvedores.
class Dog:
# Class variable (shared by all instances)
species = "Canis familiaris"
# Constructor
def __init__(self, name, age):
# Instance variables
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says Woof!"
# String representation
def __str__(self):
return f"Dog({self.name}, {self.age})"
# Official representation (for debugging)
def __repr__(self):
return f"Dog(name='{self.name}', age={self.age})"
# Create instances
buddy = Dog("Buddy", 3)
lucy = Dog("Lucy", 5)
print(buddy.bark()) # Buddy says Woof!
print(buddy.name) # Buddy
print(buddy.species) # Canis familiaris
print(str(buddy)) # Dog(Buddy, 3)Herança & Polimorfismo
Herança permite que classes reutilizem e estendam comportamento. Python suporta herança múltipla com MRO (Method Resolution Order) para resolver conflitos. Polimorfismo permite tratar diferentes tipos de forma uniforme. Use isinstance() para verificação de tipo, não type().
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement")
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# Polymorphism - same interface, different behavior
def animal_sound(animal):
print(animal.speak())
animals = [Dog("Buddy"), Cat("Whiskers")]
for a in animals:
animal_sound(a)
# Buddy says Woof!
# Whiskers says Meow!
# Multiple inheritance
class Swimmer:
def swim(self):
return "swimming"
class Flyer:
def fly(self):
return "flying"
class Duck(Animal, Swimmer, Flyer):
pass
duck = Duck("Donald")
print(duck.swim()) # swimming
print(duck.fly()) # flying
# Check inheritance
print(isinstance(duck, Animal)) # True
print(issubclass(Dog, Animal)) # TrueProperties & Encapsulamento
Python não tem private/protected verdadeiro — usa convenções. Um underscore único _ significa 'interno'. Underscore duplo __ aciona name mangling (não privacidade verdadeira). @property transforma métodos em atributos com getters/setters, habilitando validação e propriedades computadas.
class Temperature:
def __init__(self, celsius=0):
self.celsius = celsius # uses setter below
# Getter
@property
def celsius(self):
return self._celsius
# Setter
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
# Computed property
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
temp = Temperature(25)
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 100
print(temp.celsius) # 37.78...
# Name conventions:
# _name - protected (convention, not enforced)
# __name - private (name mangling: _ClassName__name)
# __name__ - dunder (reserved by Python)Métodos de Classe & Estáticos
@staticmethod é apenas uma função no namespace da classe — sem argumento implícito. @classmethod recebe a classe (cls) como primeiro argumento, útil para construtores alternativos (factory methods) e comportamento consciente de herança. Use classmethod para construtores, staticmethod para funções utilitárias.
class MathUtils:
pi = 3.14159
# Static method - no self/cls, lives in class namespace
@staticmethod
def add(a, b):
return a + b
# Class method - receives the class as first argument
@classmethod
def circle_area(cls, radius):
return cls.pi * radius ** 2
# Alternative constructor (common classmethod use)
@classmethod
def from_diameter(cls, diameter):
return cls() # would configure instance
# Static: called on class or instance, no special first arg
print(MathUtils.add(2, 3)) # 5
# Class: often used for alternative constructors
print(MathUtils.circle_area(5)) # 78.54...
# Factory pattern with classmethod
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def origin(cls):
return cls(0, 0)
@classmethod
def from_tuple(cls, coords):
return cls(*coords)
p1 = Point.origin()
p2 = Point.from_tuple((3, 4))Métodos Mágicos (Dunder)
Métodos mágicos (dunder methods) implementam sobrecarga de operadores e comportamento de protocolo. __add__ para +, __eq__ para ==, __len__ para len(), __iter__ para iteração/descompactação. Eles permitem que seus objetos funcionem com a sintaxe e funções integradas do Python naturalmente.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# String representation
def __str__(self):
return f"Vector({self.x}, {self.y})"
# Operator overloading
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Equality
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Length
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
# Make it iterable
def __iter__(self):
yield self.x
yield self.y
# Index access
def __getitem__(self, index):
return (self.x, self.y)[index]
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Vector(6, 8)
print(v1 * 3) # Vector(6, 9)
print(v1 == Vector(2, 3)) # True
print(len(v1)) # 3
x, y = v1 # unpacking via __iter__Tratamento de Erros
Try / Except / Finally
try/except/else/finally: try executa código arriscado, except captura erros, else executa se não houver exceção, finally sempre executa (limpeza). Capture exceções específicas, não 'except:' vazio. O bloco else é útil quando a limpeza deve ocorrer apenas em caso de sucesso. Exception é a base para a maioria dos erros capturáveis.
# Basic exception handling
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}") # division by zero
finally:
print("This always runs")
# Multiple exception types
try:
value = int("abc")
except (ValueError, TypeError) as e:
print(f"Conversion error: {e}")
# Different handlers for different exceptions
try:
f = open("nonexistent.txt")
data = f.read()
except FileNotFoundError:
print("File not found")
except PermissionError:
print("No permission")
except Exception as e:
print(f"Unexpected: {e}")
else:
print("No exception occurred")
f.close()
finally:
print("Cleanup (always runs)")
# Exception hierarchy
# BaseException
# ├── SystemExit
# ├── KeyboardInterrupt
# └── Exception
# ├── ValueError
# ├── TypeError
# ├── KeyError
# └── ...Lançando Exceções
Use raise para lançar exceções. 'raise' sozinho relança a exceção atual (em um bloco except). 'raise X from Y' encadeia exceções, preservando a causa original. Sempre lance tipos de exceção específicos. Evite usar exceções para fluxo de controle normal.
# Raise an exception
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero!")
return a / b
# Re-raise the current exception
def process(data):
try:
return parse(data)
except ValueError:
print("Logging parse error...")
raise # re-raises the same exception
# Raise with context (from)
try:
int("abc")
except ValueError as e:
raise RuntimeError("Failed to process input") from e
# Common built-in exceptions
raise ValueError("invalid value")
raise TypeError("wrong type")
raise KeyError("missing key")
raise IndexError("out of range")
raise RuntimeError("something went wrong")
raise NotImplementedError("override this")
raise FileNotFoundError("no such file")
# Exception with custom args
class ValidationError(Exception):
pass
raise ValidationError("field is required", "email")Exceções Personalizadas
Crie exceções personalizadas herdando de Exception (ou um built-in mais específico). Projete uma hierarquia para que chamadores possam capturar no nível certo. Adicione atributos personalizados para carregar contexto. Herde de Exception, não BaseException (que inclui SystemExit/KeyboardInterrupt).
# Custom exception hierarchy
class AppError(Exception):
"""Base exception for the application."""
pass
class DatabaseError(AppError):
def __init__(self, message, query=None):
super().__init__(message)
self.query = query
class ValidationError(AppError):
def __init__(self, field, message):
super().__init__(f"{field}: {message}")
self.field = field
self.message = message
class AuthenticationError(AppError):
pass
# Usage
def login(username, password):
if not username:
raise ValidationError("username", "is required")
if password != "secret":
raise AuthenticationError("Invalid credentials")
# Catching by hierarchy
try:
login("", "x")
except ValidationError as e:
print(f"Validation failed: {e.field}")
except AppError as e:
print(f"App error: {e}")
# Access exception info
import traceback
try:
1 / 0
except:
traceback.print_exc()
print(repr(sys.exc_info()[1]))Context Managers (with statement)
Context managers (o statement 'with') garantem limpeza via __enter__ e __exit__. Eles são essenciais para recursos como arquivos, locks e conexões de banco de dados. contextlib.contextmanager simplifica a criação usando um gerador. __exit__ pode suprimir exceções retornando True.
# Context managers handle setup and cleanup
with open("file.txt") as f:
content = f.read()
# file is automatically closed, even if an error occurs
# Multiple context managers
with open("input.txt") as fin, open("output.txt", "w") as fout:
fout.write(fin.read())
# Creating a context manager (class-based)
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.time() - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False # don't suppress exceptions
with Timer() as t:
# code to time
sum(range(1000000))
# contextlib for simpler context managers
from contextlib import contextmanager
@contextmanager
def open_db(url):
db = connect(url)
try:
yield db
finally:
db.close()
with open_db("localhost") as db:
db.query("SELECT 1")Asserções & Logging
assert statements são para depuração de invariantes — eles são removidos quando Python roda com -O (optimize). Nunca use asserções para validação de entrada. Use o módulo logging em vez de print() para código de produção — ele suporta níveis, formatação e destinos de saída.
# Assertions - for debugging (removed with -O flag)
def divide(a, b):
assert b != 0, "Divisor cannot be zero"
return a / b
# Never use assertions for data validation (they can be disabled)
# Use them for invariant checks during development
# Logging (better than print for production)
import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(message)s"
)
logger = logging.getLogger(__name__)
logger.debug("Detailed info for debugging")
logger.info("Confirmation things are working")
logger.warning("Something unexpected happened")
logger.error("A serious problem")
logger.critical("A fatal error")
# Log exceptions with traceback
try:
1 / 0
except:
logger.exception("Failed to divide") # includes tracebackFile I/O
Lendo Arquivos
Sempre use 'with' para abrir arquivos — ele os fecha automaticamente mesmo se ocorrer um erro. Especifique encoding='utf-8' para evitar problemas de codificação dependentes de plataforma. Para arquivos grandes, itere linha por linha em vez de read() para economizar memória. readlines() carrega o arquivo inteiro na memória.
# Read entire file
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# Read line by line (memory-efficient for large files)
with open("file.txt", "r") as f:
for line in f:
print(line.strip()) # strip removes trailing newline
# Read all lines into a list
with open("file.txt") as f:
lines = f.readlines() # ['line1\n', 'line2\n', ...]
# Read specific number of characters
with open("file.txt") as f:
chunk = f.read(100) # first 100 chars
# File modes:
# "r" read (default)
# "w" write (truncate)
# "a" append
# "x" exclusive create (fails if exists)
# "b" binary mode (e.g., "rb", "wb")
# "+" read and write (e.g., "r+")Escrevendo Arquivos
Modo 'w' trunca o arquivo (deleta conteúdo); use 'a' para anexar. writelines() não adiciona novas linhas — adicione-as manualmente. Use 'rb'/'wb' para arquivos binários (imagens, etc.). seek() move o cursor; tell() retorna sua posição. Sempre especifique encoding para arquivos de texto.
# Write text (overwrites existing)
with open("output.txt", "w") as f:
f.write("First line\n")
f.write("Second line\n")
# writelines doesn't add newlines
f.writelines(["line3\n", "line4\n"])
# Append to a file
with open("log.txt", "a") as f:
f.write("New log entry\n")
# Write binary data
with open("data.bin", "wb") as f:
f.write(b"\x00\x01\x02\x03")
# Read and write simultaneously
with open("file.txt", "r+") as f:
content = f.read()
f.seek(0) # move to beginning
f.write("Updated") # overwrite
f.truncate() # cut off remaining
# Check if file exists
import os
if os.path.exists("file.txt"):
print("File exists")Manipulação de Caminhos (pathlib)
pathlib (Python 3.4+) é a forma moderna e orientada a objetos de manipular caminhos — prefira-o ao os.path. O operador / junta caminhos de forma independente de plataforma. Objetos Path têm métodos read_text()/write_text() que lidam com open/close para você. rglob() busca recursivamente.
from pathlib import Path
# Create Path objects (preferred over os.path)
p = Path("src/main.py")
home = Path.home() # /home/user or C:\Users\user
cwd = Path.cwd() # current working directory
# Path components
print(p.name) # main.py
print(p.stem) # main
print(p.suffix) # .py
print(p.parent) # src
print(p.parts) # ('src', 'main.py')
# Joining paths (use / operator)
config = home / ".config" / "app" / "config.json"
# Existence and type
print(p.exists()) # True/False
print(p.is_file())
print(p.is_dir())
# Listing directories
for f in Path(".").iterdir():
print(f)
# Glob patterns
for py_file in Path(".").rglob("*.py"):
print(py_file)
# Create directories
Path("new/dir").mkdir(parents=True, exist_ok=True)
# Read/write (Path methods)
content = Path("file.txt").read_text()
Path("output.txt").write_text("Hello!")JSON
json.dumps() serializa para string, json.loads() desserializa. dump()/load() trabalham com arquivos. Use indent para pretty-printing. Objetos personalizados precisam de um serializador padrão. JSON suporta apenas tipos básicos — use default= para datetime e outros objetos complexos.
import json
# Python dict to JSON string
data = {"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}
json_str = json.dumps(data, indent=2)
print(json_str)
# JSON string to Python dict
parsed = json.loads('{"name": "Bob", "active": true}')
print(parsed["name"]) # Bob
print(parsed["active"]) # True (Python bool)
# Write JSON to file
with open("data.json", "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Read JSON from file
with open("data.json") as f:
loaded = json.load(f)
# Custom serialization (e.g., datetime)
from datetime import datetime
def json_default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError
json.dumps({"time": datetime.now()}, default=json_default)
# Type mapping:
# JSON object <-> Python dict
# JSON array <-> Python list
# JSON string <-> Python str
# JSON number <-> Python int/float
# JSON boolean <-> Python bool
# JSON null <-> Python NoneCSV & Outros Formatos
O módulo csv lida com CSV com quoting adequado. Use newline='' ao abrir arquivos CSV no Windows. DictReader/DictWriter trabalham com nomes de colunas. pickle pode serializar qualquer objeto Python, mas é específico do Python e inseguro — nunca faça unpickle de dados de fontes não confiáveis.
import csv
# Write CSV
with open("data.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["name", "age", "city"])
writer.writerows([
["Alice", 30, "NYC"],
["Bob", 25, "LA"],
])
# Read CSV
with open("data.csv") as f:
reader = csv.reader(f)
header = next(reader) # first row
for row in reader:
print(row) # ['Alice', '30', 'NYC']
# DictReader/DictWriter (column access by name)
with open("data.csv") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"])
# Pickle (Python-specific, can store any object)
import pickle
with open("data.pkl", "wb") as f:
pickle.dump({"complex": [1, 2, {"a": 3}]}, f)
with open("data.pkl", "rb") as f:
obj = pickle.load(f)
# WARNING: pickle is insecure - never unpickle untrusted data!Módulos & Pacotes
Importando Módulos
Imports trazem módulos. 'import X' mantém o namespace limpo. 'from X import Y' é conveniente, mas pode causar colisões de nomes. Aliases (import X as Y) são comuns para bibliotecas com convenções (np, pd). Evite 'from X import *' — polui o namespace.
# Import entire module
import math
print(math.sqrt(16))
# Import specific names
from datetime import datetime, timedelta
now = datetime.now()
# Import with alias
import numpy as np
import pandas as pd
# Import all names (discouraged - pollutes namespace)
# from os import *
# Conditional import (try/except)
try:
import cjson as json
except ImportError:
import json
# Check what's in a module
import os
print(dir(os)) # list all attributes
print(os.__file__) # module location
print(os.__name__) # module name
# Reload a module (during development)
import importlib
importlib.reload(my_module)Criando Módulos & Pacotes
Um módulo é um arquivo .py; um pacote é um diretório com __init__.py. O arquivo __init__.py pode ser vazio ou configurar o pacote. __all__ em __init__.py controla o que 'from package import *' exporta. Python moderno (3.3+) suporta namespace packages sem __init__.py.
# A module is just a .py file
# mymath.py
def add(a, b):
return a + b
PI = 3.14159
# A package is a directory with __init__.py
# mypackage/
# __init__.py
# module1.py
# module2.py
# subpackage/
# __init__.py
# module3.py
# __init__.py can be empty or contain package initialization
# mypackage/__init__.py
from .module1 import ClassA
from .module2 import func_b
__version__ = "1.0.0"
__all__ = ["ClassA", "func_b"]
# Using the package
from mypackage import ClassA
from mypackage.subpackage import module3
# __all__ controls 'from package import *'
# Without __all__, * imports only what's in __init__.py__name__ == '__main__'
O idioma if __name__ == '__main__' permite que um arquivo sirva tanto como script quanto como módulo. Quando executado diretamente, __name__ é '__main__'; quando importado, é o nome do módulo. Esse padrão é essencial para criar módulos reutilizáveis que também podem ser executados standalone.
# script.py
def main():
print("Running main")
def helper():
print("Helper function")
if __name__ == "__main__":
# This code only runs when the file is executed directly
# NOT when imported as a module
main()
# When you run: python script.py
# __name__ is "__main__" -> main() runs
# When you: import script
# __name__ is "script" -> main() does NOT run
# This lets the module be both a script and an importable library
# Common pattern for CLI tools
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--name", required=True)
args = parser.parse_args()
print(f"Hello, {args.name}!")
if __name__ == "__main__":
main()Destaques da Biblioteca Padrão
A biblioteca padrão do Python é enorme e batteries-included. os/sys para interação com sistema, datetime para datas, collections para contêineres especializados, itertools para ferramentas de iterador, functools para programação funcional. Explore os docs em docs.python.org/3/library/.
# os - operating system interface
import os
os.getcwd() # current directory
os.listdir(".") # list files
os.environ.get("HOME") # environment variables
# sys - system-specific
import sys
sys.argv # command-line arguments
sys.exit(0) # exit with status code
sys.path # module search path
# datetime - date and time
from datetime import datetime, timedelta
now = datetime.now()
# collections - specialized containers
from collections import Counter, defaultdict, deque
# itertools - iterator tools
from itertools import chain, cycle, repeat, product
# functools - higher-order functions
from functools import lru_cache, reduce, partial
# typing - type hints
from typing import List, Dict, Optional, Union, Any
# pathlib - path handling
from pathlib import Path
# subprocess - run external commands
import subprocess
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)Pip & Ambientes Virtuais
Sempre use ambientes virtuais para isolar dependências do projeto. venv é integrado; alternativas incluem virtualenv, conda e uv. Fixe versões em requirements.txt para reprodutibilidade. Nunca instale pacotes globalmente com --user ou como root — use um venv.
# Create a virtual environment
# $ python -m venv venv
# Activate it
# Windows: venv\Scripts\activate
# Unix: source venv/bin/activate
# Install packages
# $ pip install requests
# $ pip install requests==2.28.0
# $ pip install "requests>=2.25,<3.0"
# Install from requirements file
# $ pip install -r requirements.txt
# requirements.txt example:
# requests==2.31.0
# numpy>=1.21.0
# pandas~=2.0.0 # compatible release
# List installed packages
# $ pip list
# $ pip freeze > requirements.txt
# Uninstall
# $ pip uninstall requests
# Show package info
# $ pip show requests
# Modern alternative: uv (faster)
# $ uv pip install requestsData & Hora
Módulo datetime
O módulo datetime fornece classes date, time, datetime e timedelta. datetime.now() retorna hora local; use datetime.now(timezone.utc) para UTC. weekday() retorna 0-6 (Seg-Dom). Sempre use datetimes com fuso horário em produção para evitar ambiguidade.
from datetime import datetime, date, time, timedelta
# Current date and time
now = datetime.now() # local time
utc_now = datetime.utcnow() # UTC (deprecated in 3.12)
utc = datetime.now(timezone.utc) # preferred
# Current date
today = date.today()
# Create specific date/time
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)
# Access components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
print(now.weekday()) # 0=Monday, 6=Sunday
# From timestamp
ts = 1705315200
dt = datetime.fromtimestamp(ts)
# To timestamp
print(datetime.now().timestamp())Formatação & Parsing
strftime (string format time) converte datetime para string; strptime (string parse time) converte string para datetime. Formato ISO 8601 (isoformat/fromisoformat) é a melhor escolha para armazenar datas — é inequívoco e ordenável. Memorize códigos comuns: %Y %m %d %H %M %S.
from datetime import datetime
# Format datetime to string (strftime)
dt = datetime(2024, 1, 15, 10, 30)
print(dt.strftime("%Y-%m-%d")) # 2024-01-15
print(dt.strftime("%Y/%m/%d %H:%M")) # 2024/01/15 10:30
print(dt.strftime("%B %d, %Y")) # January 15, 2024
print(dt.strftime("%A")) # Monday
# Parse string to datetime (strptime)
dt = datetime.strptime("2024-01-15", "%Y-%m-%d")
dt = datetime.strptime("15/01/2024 10:30", "%d/%m/%Y %H:%M")
# Common format codes:
# %Y year (2024) %m month (01)
# %d day (15) %H hour (14)
# %M minute (30) %S second (00)
# %B month name %b month abbrev
# %A weekday name %a weekday abbrev
# %I 12-hour %p AM/PM
# %j day of year %U week number
# ISO format (recommended for storage)
iso = dt.isoformat() # "2024-01-15T10:30:00"
dt = datetime.fromisoformat("2024-01-15T10:30:00")timedelta & Aritmética
timedelta representa uma duração. Você pode adicionar/subtrair timedeltas de datetimes e subtrair dois datetimes para obter um timedelta. timedelta normaliza: days=1, hours=25 torna-se days=2, hours=1. total_seconds() dá toda a duração em segundos.
from datetime import datetime, timedelta
now = datetime.now()
# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
in_2_hours = now + timedelta(hours=2)
in_90_days = now + timedelta(days=90)
# Difference between dates
date1 = datetime(2024, 1, 15)
date2 = datetime(2024, 6, 18)
diff = date2 - date1
print(diff.days) # 155
print(diff.total_seconds())
# timedelta components
td = timedelta(days=5, hours=3, minutes=30)
print(td.days) # 5
print(td.seconds) # 12600 (3h 30m in seconds)
print(td.total_seconds())
# Comparisons
if now > date1:
print("now is later")
# Business day calculation (using numpy)
# import numpy as np
# business_days = np.busday_count(date1.date(), date2.date())Fusos Horários
Sempre use datetimes com fuso horário (Python 3.9+ ZoneInfo é preferido ao pytz). Armazene datas em UTC e converta para hora local apenas para exibição. Datetimes naive (sem tzinfo) causam bugs sutis. ZoneInfo usa o banco de dados de fuso horário IANA, lidando com DST automaticamente.
from datetime import datetime, timezone, timedelta
# Timezone-aware datetime
utc_time = datetime.now(timezone.utc)
print(utc_time) # 2024-01-15 10:30:00+00:00
# Create a timezone (offset-based)
tz_ny = timezone(timedelta(hours=-5), "EST")
ny_time = datetime.now(tz_ny)
# Convert between timezones
utc_time = datetime.now(timezone.utc)
ny_time = utc_time.astimezone(timezone(timedelta(hours=-5)))
tokyo_time = utc_time.astimezone(timezone(timedelta(hours=9)))
# Use zoneinfo (Python 3.9+) for IANA timezones
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York")
dt = datetime.now(tz)
print(dt.tzname()) # EST or EDT
# Common IANA timezones:
# "UTC"
# "America/New_York", "America/Los_Angeles"
# "Europe/London", "Europe/Paris"
# "Asia/Tokyo", "Asia/Shanghai"
# "Australia/Sydney"
# Best practice: store UTC, convert for display
utc_stored = datetime.now(timezone.utc)
local_display = utc_stored.astimezone(ZoneInfo("Asia/Shanghai"))Expressões Regulares
Básico do Módulo re
re.search() encontra a primeira correspondência em qualquer lugar; re.match() apenas no início; re.fullmatch() requer que a string inteira corresponda. Use raw strings (r'...') para padrões para evitar problemas de escape de barra invertida. Objetos Match fornecem group(), start(), end() e span().
import re
# re.search - find first match anywhere in string
m = re.search(r"\d{4}", "Order #2024 was placed")
if m:
print(m.group()) # 2024
print(m.start(), m.end()) # 9 13
# re.match - match at beginning of string
m = re.match(r"Hello", "Hello, World")
print(m.group()) # Hello
# re.fullmatch - entire string must match
m = re.fullmatch(r"\d+", "12345")
print(bool(m)) # True
# re.findall - all matches as list
emails = re.findall(r"\S+@\S+", text)
numbers = re.findall(r"\d+", "a1b22c333") # ['1', '22', '333']
# re.finditer - all matches as iterator (with positions)
for m in re.finditer(r"\w+", "Hello World"):
print(m.group(), m.span())
# Match object methods
m = re.search(r"(\w+)@(\w+)", "[email protected]")
print(m.group()) # user@domain (whole match)
print(m.group(1)) # user (first group)
print(m.group(2)) # domain (second group)
print(m.groups()) # ('user', 'domain')Sintaxe de Padrões
Sintaxe regex: [] para classes de caracteres, \d \w \s para conjuntos comuns, quantifiers (* + ? {}) para repetição, ^ $ \b para âncoras, () para grupos, | para alternância. Use raw strings (r'...') para que barras invertidas sejam literais. Quantifiers greedy correspondem o máximo possível; adicione ? para lazy (ex.: *?).
import re
# Character classes
re.findall(r"[aeiou]", "hello") # vowels
re.findall(r"[^aeiou]", "hello") # non-vowels
re.findall(r"[a-z]", "Hello123") # lowercase
re.findall(r"[A-Za-z0-9]", "Hi-1!") # alphanumeric
# Predefined classes
# . any char except newline
# \d digit [0-9] \D non-digit
# \w word char [a-zA-Z0-9_] \W non-word
# \s whitespace \S non-whitespace
# Quantifiers
# * 0 or more
# + 1 or more
# ? 0 or 1
# {n} exactly n
# {n,} n or more
# {n,m} between n and m
re.findall(r"\d{3}", "1234567") # ['123', '456']
re.findall(r"\d{2,4}", "12345678") # ['1234', '5678']
# Anchors
# ^ start of string $ end of string
# \b word boundary
re.findall(r"^\w+", "Hello World") # ['Hello']
re.findall(r"\b\w+\b", "hi there") # ['hi', 'there']
# Groups & alternation
re.findall(r"(cat|dog)", "cat and dog") # ['cat', 'dog']
re.findall(r"(\w+)@(\w+\.\w+)", "[email protected]")Substituição & Divisão
re.sub() substitui correspondências — use backreferences (\1, \2) para referenciar grupos, ou uma função para substituição dinâmica. re.split() é mais poderoso que str.split() — aceita padrões regex. Grupos de captura no padrão são incluídos no resultado. Use re.subn() para obter (resultado, contagem).
import re
# re.sub - replace matches
result = re.sub(r"\d+", "#", "a1b22c333")
# 'a#b#c#'
# Replace with count limit
result = re.sub(r"\d", "X", "a1b2c3", count=2)
# 'aXbXc3'
# Use backreferences in replacement
result = re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@domain")
# 'domain.user'
# Use function as replacement
def upper(m):
return m.group().upper()
result = re.sub(r"\b[a-z]", upper, "hello world")
# 'Hello World' (capitalize first letter of each word)
# re.split - split by pattern
parts = re.split(r"[,;\s]+", "a, b; c d")
# ['a', 'b', 'c', 'd']
# Split with capture groups (keeps delimiters)
parts = re.split(r"([,;])", "a,b;c")
# ['a', ',', 'b', ';', 'c']
# Split with maxsplit
parts = re.split(r",", "a,b,c,d", maxsplit=2)
# ['a', 'b', 'c,d']Compilação & Flags
Compile padrões com re.compile() ao usá-los repetidamente — é mais rápido. Flags modificam comportamento: IGNORECASE, MULTILINE, DOTALL, VERBOSE (permite comentários/espaços em branco em padrões). Named groups (?P<name>...) melhoram legibilidade. Lookahead (?=) e lookbehind (?<=) correspondem sem consumir.
import re
# Compile pattern for reuse (faster when used many times)
email_re = re.compile(r"^[\w.+-]+@([\w-]+\.)+[\w-]+$")
print(email_re.match("[email protected]")) # match object
print(email_re.match("invalid")) # None
# Common flags
re.IGNORECASE # case-insensitive
re.MULTILINE # ^ and $ match line boundaries
re.DOTALL # . matches newline too
re.VERBOSE # allow whitespace & comments in pattern
# Combine flags with |
pattern = re.compile(r"""
^ # start of line
(\w+) # capture word
\s+ # whitespace
(\d+) # capture number
""", re.VERBOSE | re.MULTILINE)
# Named groups (more readable)
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2024-01")
print(m.group("year")) # 2024
print(m.group("month")) # 01
print(m.groupdict()) # {'year': '2024', 'month': '01'}
# Lookahead/lookbehind
re.findall(r"\d+(?= dollars)", "100 dollars, 200 euros")
# ['100'] (positive lookahead)
re.findall(r"(?<=\$)\d+", "$100 and $200")
# ['100', '200'] (positive lookbehind)Async & Concorrência
Básico do asyncio
asyncio é o framework de I/O assíncrono do Python. 'async def' define uma coroutine; 'await' suspende até que um resultado esteja pronto. asyncio.run() inicia o event loop. asyncio.gather() executa coroutines concorrentemente. Coroutines habilitam I/O de alta concorrência sem threads.
import asyncio
# Define a coroutine
async def greet(name, delay):
await asyncio.sleep(delay) # non-blocking sleep
return f"Hello, {name}!"
# Run a coroutine
async def main():
result = await greet("Alice", 1)
print(result)
# Run the event loop
asyncio.run(main())
# Concurrent execution with gather
async def main():
# Run coroutines concurrently
results = await asyncio.gather(
greet("Alice", 2),
greet("Bob", 1),
greet("Carol", 3)
)
print(results) # all complete after 3 seconds (max delay)
asyncio.run(main())
# asyncio.create_task - schedule without awaiting immediately
async def main():
task = asyncio.create_task(greet("Alice", 1))
# do other work here
result = await task # await when needed
print(result)Async HTTP & Timeouts
asyncio.timeout() (Python 3.11+) cancela operações que demoram muito. Para HTTP, use aiohttp (async) em vez de requests (sync). asyncio.Queue habilita padrões producer-consumer. Async é ideal para trabalho I/O-bound (rede, disco) — não trabalho CPU-bound.
import asyncio
# Async timeout
async def fetch_with_timeout(url, timeout=5):
try:
async with asyncio.timeout(timeout):
# simulate async operation
await asyncio.sleep(2)
return f"Data from {url}"
except asyncio.TimeoutError:
return "Request timed out"
# Using aiohttp (third-party) for HTTP
# import aiohttp
#
# async def fetch(url):
# async with aiohttp.ClientSession() as session:
# async with session.get(url) as response:
# return await response.text()
# Producer-consumer pattern
async def producer(queue):
for i in range(5):
await asyncio.sleep(0.1)
await queue.put(f"item-{i}")
await queue.put(None) # sentinel
async def consumer(queue):
while True:
item = await queue.get()
if item is None:
break
print(f"Processed: {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(producer(queue), consumer(queue))
asyncio.run(main())Threading
Threading é para concorrência I/O-bound (rede, I/O de arquivo). O GIL do Python impede execução paralela verdadeira de CPU em threads. Use Lock para proteger estado compartilhado de race conditions. Para trabalho CPU-bound, use multiprocessing. Threads compartilham memória; processos não.
import threading
import time
# Basic threading
def worker(name, delay):
print(f"Worker {name} starting")
time.sleep(delay)
print(f"Worker {name} done")
# Create and start threads
t1 = threading.Thread(target=worker, args=("A", 2))
t2 = threading.Thread(target=worker, args=("B", 1))
t1.start()
t2.start()
# Wait for threads to complete
t1.join()
t2.join()
print("All done")
# Thread with Lock (for shared state)
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock: # acquire/release lock
counter += 1
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter}") # 500000 (correct with lock)Multiprocessing
Multiprocessing contorna o GIL para paralelismo verdadeiro de CPU — cada processo tem seu próprio interpretador Python. Use Pool para operações map paralelas. concurrent.futures fornece uma API unificada para threads e processos. Sempre proteja com if __name__ == '__main__' no Windows.
from multiprocessing import Process, Pool, Queue
import os
# Basic process
def worker(name):
print(f"Process {name} PID: {os.getpid()}")
if __name__ == "__main__":
p = Process(target=worker, args=("A",))
p.start()
p.join()
# Process pool for parallel work
def square(x):
return x * x
if __name__ == "__main__":
with Pool(4) as pool: # 4 worker processes
results = pool.map(square, range(10))
print(results) # [0, 1, 4, 9, ..., 81]
# Asynchronous map
with Pool(4) as pool:
result = pool.map_async(square, range(10))
print(result.get()) # blocks until done
# concurrent.futures (higher-level API)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
with ProcessPoolExecutor() as executor:
results = list(executor.map(square, range(10)))
with ThreadPoolExecutor() as executor:
futures = [executor.submit(square, i) for i in range(10)]
results = [f.result() for f in futures]Decoradores
Decorador Básico
Decoradores envolvem uma função para estender ou modificar seu comportamento sem alterar o código-fonte original. A @syntax é açúcar sintático para atribuir o resultado da chamada do decorador de volta ao nome da função. Use *args, **kwargs no wrapper para que funcione com qualquer assinatura.
# A decorator is a function that takes a function and returns a new function
def uppercase_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_result
def greet(name):
return f"hello, {name}"
print(greet("world")) # HELLO, WORLD
# @uppercase_result is sugar for: greet = uppercase_result(greet)functools.wraps (Preservar Metadados)
Sem @wraps, a função envolvida perde seu __name__, __doc__ e assinatura originais — ferramentas de depuração e help() mostram 'wrapper'. Sempre use @functools.wraps(func) dentro de decoradores para preservar metadados. Essa é uma prática recomendada quase universal.
from functools import wraps
def log_calls(func):
@wraps(func) # copies __name__, __doc__, __module__
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}({args}, {kwargs})")
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' (not 'wrapper')
print(add.__doc__) # 'Add two numbers.'
help(add) # shows original docstringDecorador com Argumentos
Quando um decorador recebe argumentos, você precisa de três níveis de aninhamento: a factory (recebe args), o decorador (recebe a função) e o wrapper (recebe args de chamada). @repeat(3) chama repeat(3) primeiro, que retorna o decorador, que então é aplicado à função.
# A decorator factory: returns the actual decorator
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def say_hi(name):
print(f"Hi, {name}!")
say_hi("Alice")
# Hi, Alice! (printed 3 times)Decorador Baseado em Classe
Decoradores de classe usam __init__ para armazenar a função e __call__ para interceptar invocações. Eles são ideais quando o decorador precisa manter estado (como um contador de chamadas ou cache). A instância da classe substitui a função, então chamá-la aciona __call__.
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
wraps(func)(self) # preserve metadata
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} called {self.count} times")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # count=1
say_hello() # count=2
say_hello() # count=3
print(say_hello.count) # 3Decoradores Integrados (@property, @staticmethod, @classmethod)
@property transforma um método em um atributo computado (acessado sem parênteses). @classmethod recebe a classe como primeiro argumento — perfeito para construtores alternativos. @staticmethod não recebe argumento implícito — apenas uma função que acontece de viver no namespace da classe. Juntos eles formam a espinha dorsal do OOP pythônico.
class Circle:
pi = 3.14159
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return Circle.pi * self._radius ** 2
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)
@staticmethod
def is_valid_radius(r):
return r >= 0
c = Circle(5)
print(c.area) # 78.54 (no parentheses!)
c.radius = 10 # uses the setter
c2 = Circle.from_diameter(20) # alternative constructorDecoradores Empilhados
Ao empilhar decoradores, eles são aplicados de baixo para cima (o mais próximo da função executa primeiro), mas executam de cima para baixo no momento da chamada. Então @bold envolve @italic que envolve greet. O resultado é aninhado como camadas de cebola. A ordem importa — invertê-los muda o aninhamento da saída.
from functools import wraps
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Hello, {name}"
print(greet("World"))
# <b><i>Hello, World</i></b>
# Applied bottom-up: italic first, then boldGeradores & Iteradores
Funções Geradoras (yield)
Geradores produzem valores de forma preguiçosa usando yield — eles pausam a execução após cada yield e retomam quando next() é chamado. Isso os torna eficientes em memória para sequências grandes ou infinitas, já que apenas um valor existe na memória por vez. Uma vez esgotado, um gerador não pode ser reutilizado.
# A generator uses 'yield' to produce values lazily, one at a time
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
gen = count_up_to(5)
print(next(gen)) # 1
print(next(gen)) # 2
print(list(gen)) # [3, 4, 5] (exhausts the rest)
# Generators are memory-efficient: they don't build the whole list
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Expressões Geradoras
Expressões geradoras são o equivalente preguiçoso das list comprehensions — use parênteses em vez de colchetes. Elas usam memória constante independentemente do tamanho, tornando-as ideais para sum(), max(), any(), ou alimentar outros iteradores. Prefira-as a list comprehensions quando não precisar de acesso aleatório.
# Like list comprehensions, but lazy (uses parentheses)
squares_list = [x ** 2 for x in range(10)] # builds full list
squares_gen = (x ** 2 for x in range(10)) # lazy generator
print(squares_gen) # <generator object>
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# Memory comparison
import sys
print(sys.getsizeof([x for x in range(10000)])) # ~87616 bytes
print(sys.getsizeof((x for x in range(10000)))) # ~200 bytes (constant!)
# Use in sum(), list(), any() etc.
total = sum(x ** 2 for x in range(100)) # no extra list created
print(total) # 328350Protocolo Iterador (__iter__, __next__)
O protocolo iterador requer __iter__ (retorna um iterador) e __next__ (retorna o próximo valor ou levanta StopIteration). Iterables podem ser iterados; iteradores produzem valores um por vez. Para iterables reutilizáveis, separe o iterable (retorna um novo iterador) do iterador (mantém o estado).
class Range2:
"""A custom iterator that yields even numbers."""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self # the object is its own iterator
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 2
return value
r = Range2(0, 10)
for num in r:
print(num) # 0, 2, 4, 6, 8
# An iterable returns a fresh iterator each time __iter__ is called.
# An iterator returns itself and raises StopIteration when exhausted.send(), throw(), close()
Métodos avançados de gerador habilitam comunicação bidirecional: send() passa um valor para o gerador (torna-se o resultado de yield), throw() injeta uma exceção no ponto de yield, e close() termina o gerador. Você deve 'preparar' o gerador com next() antes de enviar. Esses métodos alimentam coroutines e frameworks async.
def echo():
while True:
received = yield # yield without a value, receives via send()
print(f"Echo: {received}")
gen = echo()
next(gen) # prime the generator (advance to first yield)
gen.send("hello") # Echo: hello
gen.send("world") # Echo: world
# throw() injects an exception at the yield point
def safe_gen():
try:
while True:
yield
except ValueError:
print("Caught ValueError inside generator")
g = safe_gen()
next(g)
g.throw(ValueError, "boom") # Caught ValueError inside generator
# close() stops the generator (raises GeneratorExit)
gen.close()Pipelines de Geradores
Pipelines de geradores encadeiam produtores preguiçosos para que os dados fluam por estágios um item por vez — cada item é totalmente processado antes do próximo ser lido. Isso evita construir listas intermediárias e é a base do processamento de streaming de dados. Pipes do Unix funcionam da mesma forma conceitualmente.
# Chain generators to build data-processing pipelines
def numbers():
for i in range(1, 11):
yield i
def squared(seq):
for n in seq:
yield n ** 2
def evens(seq):
for n in seq:
if n % 2 == 0:
yield n
# Each stage processes one item at a time — no intermediate lists
pipeline = evens(squared(numbers()))
print(list(pipeline)) # [4, 16, 36, 64, 100]
# Equivalent with generator expressions:
result = (n for n in (x ** 2 for x in range(1, 11)) if n % 2 == 0)
print(list(result)) # [4, 16, 36, 64, 100]yield from (Delegação)
yield from delega todos os yields (e send/throw/close) a um sub-iterador, achatando estruturas aninhadas e compondo coroutines. É especialmente poderoso para geradores recursivos — o caso de uso clássico é achatar listas arbitrariamente aninhadas. Em código async, 'await' é construído sobre o mesmo conceito.
# yield from delegates to a sub-iterator (Python 3.3+)
def flatten(nested):
for item in nested:
if isinstance(item, (list, tuple)):
yield from flatten(item) # recursive delegation
else:
yield item
data = [1, [2, 3, [4, 5]], 6, [7, [8, [9]]]]
print(list(flatten(data)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# yield from also forwards send()/throw() to the sub-generator,
# making it essential for coroutine composition.Context Managers
Básico do Statement with
O statement with garante que recursos sejam liberados (arquivos fechados, locks liberados, conexões devolvidas) mesmo quando exceções ocorrem. Ele chama __enter__ no início e __exit__ no final. Sempre prefira 'with' a try/finally manual para gerenciamento de recursos — é mais seguro e legível.
# 'with' guarantees cleanup even if an exception occurs
with open("data.txt", "r") as f:
content = f.read()
# f.close() is called automatically here, even if read() raised
# Without 'with' you must manually close:
f = open("data.txt", "r")
try:
content = f.read()
finally:
f.close() # easy to forget!
# Common built-in context managers:
with open("out.txt", "w") as f, open("in.txt") as g:
f.write(g.read()) # both files close automaticallyContext Manager Personalizado (Classe)
Um context manager baseado em classe implementa __enter__ (setup, retorna o objeto de contexto) e __exit__(exc_type, exc_val, exc_tb) (limpeza). Os argumentos __exit__ recebem informação de exceção se uma ocorreu; retornar True a suprime. Esse padrão é ideal para setup/teardown complexo como transações de banco de dados.
class Timer:
def __init__(self, label="Timer"):
self.label = label
def __enter__(self):
import time
self.start = time.perf_counter()
return self # value bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.perf_counter() - self.start
print(f"{self.label}: {elapsed:.4f}s")
# Return False (or None) to propagate exceptions
# Return True to suppress the exception
return False
with Timer("Processing"):
total = sum(i ** 2 for i in range(1_000_000))
# Processing: 0.1234scontextlib.contextmanager
contextlib.contextmanager transforma uma função geradora em um context manager — código antes de yield é __enter__, código após yield (em finally) é __exit__. Isso é mais conciso que uma classe para casos simples. Faça yield de um valor para fornecê-lo à variável 'as'. Use try/finally para garantir limpeza.
from contextlib import contextmanager
import time
@contextmanager
def timer(label="Timer"):
start = time.perf_counter()
try:
yield # code inside the 'with' block runs here
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with timer("My task"):
sum(i ** 2 for i in range(1_000_000))
# My task: 0.1234s
# You can also yield a value to bind with 'as'
@contextmanager
def open_db(path):
db = connect(path)
try:
yield db
finally:
db.close()
with open_db("app.db") as db:
db.query("SELECT 1")Múltiplos Context Managers
Python 3.10+ permite statements 'with' multi-linha entre parênteses para sintaxe mais limpa. Para números dinâmicos de context managers, contextlib.ExitStack gerencia-os como um grupo e desfaz todos em ordem reversa. ExitStack é essencial quando o número de recursos não é conhecido até tempo de execução.
# Python 3.10+ supports parenthesized context managers
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
):
fout.write(fin.read())
# Pre-3.10: nest them or use contextlib.ExitStack
from contextlib import ExitStack
files = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
handles = [stack.enter_context(open(f)) for f in files]
for h in handles:
print(h.read())Utilitários contextlib (suppress, redirect)
contextlib.suppress substitui try/except/pass para exceções esperadas — muito mais legível. redirect_stdout/redirect_stderr capturam saída que de outra forma iria para o console, útil para testes ou logging. Esses utilitários evitam boilerplate e tornam a intenção explícita.
from contextlib import suppress, redirect_stdout, redirect_stderr
import io, warnings
# suppress: ignore specific exceptions (cleaner than try/except/pass)
with suppress(FileNotFoundError):
os.remove("temp.txt") # no error if file doesn't exist
# redirect_stdout: capture print output
buffer = io.StringIO()
with redirect_stdout(buffer):
print("This goes to the buffer, not console")
captured = buffer.getvalue()
# redirect_stderr: capture error/warning output
err_buf = io.StringIO()
with redirect_stderr(err_buf):
warnings.warn("a warning")
print(err_buf.getvalue()) # the warning text
# Also: contextlib.chdir (3.11+) to temporarily change directory
# from contextlib import chdir
# with chdir("/tmp"): ...Async Context Managers
Async context managers usam __aenter__/__aexit__ (note o prefixo 'a') e o statement 'async with'. Eles são essenciais para gerenciar recursos async como conexões de banco de dados ou sessões HTTP (ex.: aiohttp.ClientSession). A limpeza executa mesmo se um await ou exceção ocorrer dentro do bloco.
import asyncio
class AsyncDB:
async def __aenter__(self):
print("connecting...")
await asyncio.sleep(0.1) # simulate async connect
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("closing...")
await asyncio.sleep(0.1) # simulate async close
return False
async def query(self, sql):
return f"result of: {sql}"
async def main():
async with AsyncDB() as db:
result = await db.query("SELECT 1")
print(result)
asyncio.run(main())
# connecting...
# result of: SELECT 1
# closing...Type Hints
Anotações Básicas de Variável & Função
Type hints documentam tipos esperados, mas NÃO são aplicados em tempo de execução — Python permanece dinamicamente tipado. Use um checker estático como mypy ou pyright para capturar erros de tipo antes de executar. Os generics integrados (list[str], dict[str, int]) requerem Python 3.9+; versões mais antigas precisam typing.List, typing.Dict.
# Variable annotations (Python 3.6+)
name: str = "Alice"
age: int = 30
scores: list[float] = [95.5, 88.0, 92.3]
config: dict[str, int] = {"timeout": 30}
# Function annotations
def greet(name: str, excited: bool = False) -> str:
punctuation = "!" if excited else "."
return f"Hello, {name}{punctuation}"
print(greet("World", excited=True)) # Hello, World!
# Annotations are optional and NOT enforced at runtime
def add(a: int, b: int) -> int:
return a + b
add("2", "3") # runs fine, returns "23" (no error!)Módulo typing (List, Dict, Tuple, Optional)
O módulo typing fornece aliases genéricos para Python mais antigo. Desde 3.9, você pode usar tipos integrados diretamente (list[str] em vez de List[str]). Optional[X] é abreviação para Union[X, None] — use-o para sinalizar que uma função pode retornar None, forçando chamadores a lidar com o caso None.
from typing import List, Dict, Tuple, Set, FrozenSet
# Pre-3.9 style (still works, needed for older Python)
names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 95}
point: Tuple[int, int] = (10, 20)
mixed: Tuple[str, int, float] = ("a", 1, 2.0)
variadic: Tuple[int, ...] = (1, 2, 3) # variable-length
# Python 3.9+ built-in generics (preferred)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95}
point: tuple[int, int] = (10, 20)
# Optional means "could be None"
from typing import Optional
def find_user(uid: int) -> Optional[str]:
if uid == 1:
return "Alice"
return None # could also just 'return'Tipos Union & Literal
Union[X, Y] (ou X | Y em 3.10+) significa que um valor pode ser de qualquer tipo. Literal restringe um valor a constantes específicas — ótimo para string enums sem a sobrecarga de enum, e para dispatch de função sobrecarregada. mypy usa Literal para estreitar tipos e capturar argumentos inválidos em tempo de verificação.
from typing import Union, Literal, overload
# Union: value can be one of several types
def process(data: Union[str, bytes]) -> str:
if isinstance(data, bytes):
return data.decode("utf-8")
return data
# Python 3.10+ union syntax with | (preferred)
def process2(data: str | bytes) -> str:
if isinstance(data, bytes):
return data.decode("utf-8")
return data
# Literal: restrict to specific constant values
def set_mode(mode: Literal["r", "w", "a"]) -> None:
print(f"Mode set to {mode}")
set_mode("r") # OK
# set_mode("x") # mypy error: not a valid literal
# Literal for boolean-like flags
Direction = Literal["up", "down", "left", "right"]TypeVar & Generics
TypeVar cria variáveis de tipo genéricas para que funções e classes possam preservar relações de tipo (ex.: 'retorna o mesmo tipo da entrada'). Use bound= para restringir a um subtipo, ou especifique constraints como TypeVar('T', int, float). Classes genéricas usam Generic[T] como base para se tornarem contêineres parametrizados.
from typing import TypeVar, Generic, List
T = TypeVar("T") # a generic type variable
def first(items: List[T]) -> T:
return items[0]
# Type inference: T is bound to the argument's type
x: int = first([1, 2, 3]) # T = int
y: str = first(["a", "b", "c"]) # T = str
# Bounded TypeVar: T must be a subtype of Number
from typing import TypeVar
from numbers import Number
N = TypeVar("N", bound=Number)
def sum_all(values: list[N]) -> N:
total = values[0]
for v in values[1:]:
total = total + v
return total
# Generic class
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
s: Stack[int] = Stack()
s.push(1)
# s.push("x") # mypy errorCallable, Type Aliases & Protocols
Callable[[int, str], bool] descreve uma função que recebe int e str, retornando bool. Type aliases dão nomes descritivos a tipos complexos. Protocol habilita tipagem estrutural (duck typing) — qualquer objeto com os métodos corretos satisfaz o protocolo, sem herança necessária. Essa é a resposta do Python a interfaces.
from typing import Callable, Protocol, TypeAlias
# Callable signature: Callable[[ArgTypes], ReturnType]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
apply(lambda x, y: x + y, 3, 4) # 7
# Type aliases (3.12+ uses 'type' statement; older uses assignment)
type Vector = list[float] # 3.12+
Vector2: TypeAlias = list[float] # 3.10+
def magnitude(v: Vector) -> float:
return sum(x ** 2 for x in v) ** 0.5
# Protocol: structural typing (duck typing with static checks)
class Drawable(Protocol):
def draw(self) -> None: ...
def render(obj: Drawable) -> None:
obj.draw() # any object with a draw() method works
class Circle:
def draw(self) -> None:
print("drawing circle")
render(Circle()) # OK — Circle has draw()Verificação de Tipos com mypy
mypy é o checker de tipo estático mais popular para Python — ele analisa type hints sem executar o código. Captura bugs de tratamento de None, tipos de argumentos errados e returns ausentes. Comece com tipagem gradual: adicione hints a código novo e rode mypy no CI. Use --strict para novos projetos para aplicar anotações abrangentes.
# Save as example.py, then run: mypy example.py
from typing import Optional
def divide(a: float, b: float) -> Optional[float]:
if b == 0:
return None
return a / b
result = divide(10, 0)
# Without checking, this would crash at runtime:
# print(result + 1) # TypeError: NoneType + int
# mypy catches it:
# error: Unsupported operand types for + ("None" and "int")
# fix: check for None first
if result is not None:
print(result + 1)
# Run strict mode for maximum safety:
# mypy --strict example.py
# Common strict flags: --disallow-untyped-defs, --no-implicit-optionalData Classes
@dataclass Básico
@dataclass gera automaticamente __init__, __repr__ e __eq__ com base em campos anotados — eliminando boilerplate para classes de dados. É ideal para value objects, configs, DTOs e records. Disponível desde Python 3.7. Campos devem ter anotações de tipo; a anotação define o campo.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1) # Point(x=1.0, y=2.0) — auto __repr__
print(p1 == p2) # True — auto __eq__ (compares fields)
print(p1.x) # 1.0
# Without @dataclass you'd write all this boilerplate:
# class Point:
# def __init__(self, x, y): self.x = x; self.y = y
# def __repr__(self): ...
# def __eq__(self, other): ...Valores Padrão & default_factory
Defaults mutáveis (lists, dicts, sets) devem usar field(default_factory=list) — usar [] diretamente compartilharia uma lista entre todas as instâncias, um bug clássico. default_factory é chamado uma vez por instância para criar um objeto novo. Defaults imutáveis simples (int, str, bool, None) podem ser atribuídos diretamente.
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
grade: str = "A" # simple default
tags: list[str] = field(default_factory=list) # mutable default!
scores: dict[str, int] = field(default_factory=dict)
s = Student("Alice")
print(s) # Student(name='Alice', grade='A', tags=[], scores={})
s.tags.append("honors")
s2 = Student("Bob")
print(s2.tags) # [] — each instance gets its own list
# NEVER use [] or {} as a direct default — all instances would share
# the same mutable object (classic Python pitfall).frozen, eq & order
frozen=True torna um dataclass imutável — campos não podem ser reatribuídos, e a instância se torna hashable (utilizável como chaves de dict ou membros de set). order=True adiciona métodos de comparação para ordenação. Combine frozen=True com order=True para value types imutáveis e ordenáveis como coordenadas, cores ou versões.
from dataclasses import dataclass
# frozen=True makes instances immutable (hashable, usable as dict keys)
@dataclass(frozen=True)
class Color:
r: int
g: int
b: int
c = Color(255, 0, 0)
# c.r = 128 # FrozenInstanceError!
print(hash(c)) # works — frozen dataclasses are hashable
# order=True generates __lt__, __le__, __gt__, __ge__ for sorting
@dataclass(order=True)
class Priority:
level: int
tasks = [Priority(3), Priority(1), Priority(2)]
tasks.sort()
print(tasks) # [Priority(level=1), Priority(level=2), Priority(level=3)]
# Common combo: frozen + order for immutable comparable values
@dataclass(frozen=True, order=True)
class Version:
major: int
minor: int__post_init__ & customização de field
__post_init__ executa automaticamente após o __init__ gerado — use-o para computar campos derivados, validar valores ou fazer setup. field(init=False) cria um campo não presente no construtor (bom para valores computados/em cache). field(repr=False, compare=False) oculta campos de repr e verificações de igualdade.
from dataclasses import dataclass, field
@dataclass
class User:
email: str
_email_normalized: str = field(init=False, repr=False)
id: int = field(default=0)
def __post_init__(self):
# runs after __init__; compute derived fields here
self._email_normalized = self.email.strip().lower()
u = User(" [email protected] ")
print(u.email) # ' [email protected] '
print(u._email_normalized) # '[email protected]'
# field(init=False) excludes a field from __init__
# field(repr=False) hides it from the repr
# field(compare=False) excludes from __eq__/__hash__
# field(metadata={...}) attaches custom metadataHerança & slots
Dataclasses suportam herança — campos filho são anexados após campos pai, e você pode sobrescrever defaults pai. Nota: um campo com default no pai não pode ser seguido por um campo sem default no filho. slots=True (3.10+) impede adicionar atributos arbitrários e reduz significativamente a memória por instância — ideal para milhões de objetos pequenos.
from dataclasses import dataclass
@dataclass
class Animal:
name: str
sound: str = "..."
@dataclass
class Dog(Animal):
breed: str = "unknown"
sound: str = "Woof" # override parent default
d = Dog("Rex", breed="Labrador")
print(d) # Dog(name='Rex', sound='Woof', breed='Labrador')
# Python 3.10+: slots=True saves memory (no __dict__)
@dataclass(slots=True)
class Pixel:
r: int
g: int
b: int
p = Pixel(0, 128, 255)
# p.new_field = 1 # AttributeError — slots prevent arbitrary attrs
# Saves ~40-50% memory vs regular dataclass for many instancesCollections & Itertools
namedtuple
namedtuple cria subclasses de tuple com campos nomeados — tão eficientes em memória quanto tuples, mas muito mais legíveis. Elas são imutáveis, então use _replace() para criar cópias modificadas. Prefira typing.NamedTuple para código novo, já que suporta anotações de tipo e valores padrão. Ótimo para retornar múltiplos valores de funções.
from collections import namedtuple
# Lightweight immutable class with named fields
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4 — access by name
print(p[0], p[1]) # 3 4 — also by index
print(p._asdict()) # {'x': 3, 'y': 4}
# More memory-efficient than a full class
# Use _replace to create a modified copy (immutable!)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=4)
# Python 3.6+ typing.NamedTuple for type hints:
from typing import NamedTuple
class Point3D(NamedTuple):
x: float
y: float
z: float = 0.0Counter
Counter é uma subclasse de dict para contar objetos hashable — perfeito para análise de frequência, histogramas e votação. most_common(n) retorna os top n itens. Chaves ausentes retornam 0 em vez de levantar KeyError. Counter suporta +, -, &, | para aritmética de conjuntos em contagens.
from collections import Counter
# Count hashable items
words = "the cat sat on the mat the cat".split()
c = Counter(words)
print(c) # Counter({'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1})
# Most common elements
print(c.most_common(2)) # [('the', 3), ('cat', 2)]
# Arithmetic on counters
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2}) (drops zero/negatives)
# Missing keys return 0 (not KeyError)
print(c["dog"]) # 0
# Update and elements
c.update(["cat", "cat"])
print(sorted(c.elements())) # ['cat','cat','cat','cat','mat','on','sat','the','the','the']defaultdict
defaultdict cria automaticamente chaves ausentes com um valor padrão de uma função factory — list para agrupamento, int para contagem, set para desduplicação. Isso elimina o boilerplate 'if key not in dict'. A factory é chamada apenas quando uma chave está ausente, não em cada acesso.
from collections import defaultdict
# Group items by key without checking if key exists
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_first = defaultdict(list)
for w in words:
by_first[w[0]].append(w)
print(dict(by_first))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
# Counting with int (default 0)
counts = defaultdict(int)
for w in words:
counts[w[0]] += 1
print(dict(counts)) # {'a': 2, 'b': 2, 'c': 1}
# Nested defaultdicts
tree = defaultdict(lambda: defaultdict(list))
tree["2024"]["Jan"].append("event1")
# vs regular dict: avoids the key-check boilerplate
# d = {}
# for w in words:
# if w[0] not in d:
# d[w[0]] = []
# d[w[0]].append(w)OrderedDict & deque
deque fornece append/pop O(1) em ambas as extremidades — use-o para filas, BFS e sliding windows em vez de listas (list.pop(0) é O(n)). Com maxlen, deque descarta automaticamente itens antigos, perfeito para buffers limitados. OrderedDict é menos necessário desde 3.7 (dicts são ordenados), mas seus move_to_end e popitem ainda são exclusivamente úteis para caches LRU.
from collections import OrderedDict, deque
# deque: double-ended queue, O(1) append/pop at both ends
dq = deque([1, 2, 3], maxlen=5)
dq.appendleft(0) # deque([0, 1, 2, 3])
dq.append(4) # deque([0, 1, 2, 3, 4])
dq.append(5) # deque([1, 2, 3, 4, 5]) — oldest dropped (maxlen!)
print(dq.popleft()) # 1
print(dq) # deque([2, 3, 4, 5])
# deque is ideal for queues, BFS, sliding windows
from collections import deque
queue = deque(["task1", "task2"])
queue.append("task3")
next_task = queue.popleft() # FIFO — O(1) vs list.pop(0) which is O(n)
# OrderedDict: remembers insertion order (regular dicts do too in 3.7+,
# but OrderedDict has move_to_end and equality is order-sensitive)
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a") # move to last
print(list(od)) # ['b', 'a']
od.popitem(last=False) # pop first item (FIFO)itertools: chain, product, combinations, permutations
itertools fornece ferramentas rápidas e eficientes em memória para combinatória. chain achata iterables de forma preguiçosa. product dá produtos cartesianos (substitui loops for aninhados). combinations/permutations geram seleções sem construir a lista completa — essencial para entradas grandes ou infinitas. Todos retornam iteradores, então envolva em list() para visualizar.
from itertools import chain, product, combinations, permutations
# chain: flatten multiple iterables
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3, 4]])) # [1, 2, 3, 4]
# product: Cartesian product (nested loops)
list(product([1, 2], ["a", "b"]))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
list(product("AB", repeat=2)) # [('A','A'),('A','B'),('B','A'),('B','B')]
# combinations: unordered selections (no repeats)
list(combinations("ABC", 2)) # [('A','B'),('A','C'),('B','C')]
list(combinations("AAA", 2)) # [('A','A'),('A','A'),('A','A')]
# permutations: ordered arrangements
list(permutations("ABC", 2)) # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]
# combinations_with_replacement: allow picking same element
from itertools import combinations_with_replacement
list(combinations_with_replacement("AB", 2)) # [('A','A'),('A','B'),('B','B')]itertools: groupby, accumulate, starmap
groupby agrupa elementos consecutivos que compartilham uma chave — ordene pela chave primeiro ou você obterá múltiplos grupos para a mesma chave. accumulate produz totais/produtos acumulados. islice, takewhile e dropwhile são alternativas preguiçosas a slicing e filtering que funcionam em qualquer iterador, incluindo infinitos.
from itertools import groupby, accumulate, starmap, islice, takewhile, dropwhile
# groupby: group consecutive items by a key (sort first!)
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
data.sort(key=lambda x: x[0]) # MUST sort by key first
for key, group in groupby(data, key=lambda x: x[0]):
print(key, list(group))
# A [('A',1),('A',2),('A',5)]
# B [('B',3),('B',4)]
# accumulate: running aggregate (sum by default)
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10]
import operator
list(accumulate([1, 2, 3, 4], operator.mul)) # [1, 2, 6, 24]
# starmap: unpack args from tuples before calling
list(starmap(pow, [(2, 3), (3, 2), (10, 3)])) # [8, 9, 1000]
# islice: slice an iterator (doesn't support negative indices)
list(islice(range(100), 5, 10)) # [5, 6, 7, 8, 9]
# takewhile / dropwhile: filter by predicate
list(takewhile(lambda x: x < 5, [1, 4, 6, 3, 8])) # [1, 4]
list(dropwhile(lambda x: x < 5, [1, 4, 6, 3, 8])) # [6, 3, 8]functools: lru_cache, partial, reduce
lru_cache memoiza resultados — speedups dramáticos para funções puras recursivas ou caras; cache_info() mostra estatísticas de hit/miss. partial pré-preenche argumentos para criar callables especializados. reduce aplica uma função cumulativamente (embora sum(), any(), all() frequentemente o substituam). cached_property computa uma vez e então armazena em cache na instância.
from functools import lru_cache, partial, reduce
import operator
# lru_cache: memoize function results (Least Recently Used)
@lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # instant (without cache: impossibly slow)
print(fib.cache_info()) # CacheInfo(hits=98, misses=101, ...)
# partial: fix some arguments, create a new callable
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(3)) # 27
# reduce: cumulatively apply a function, reducing to one value
product = reduce(operator.mul, [1, 2, 3, 4]) # 24
# Equivalent: ((1*2)*3)*4
# Python 3.8+: cached_property for lazy computed attributes
from functools import cached_property
class Data:
@cached_property
def expensive(self):
print("computing...")
return [i ** 2 for i in range(1000000)]Processamento de JSON & CSV
json.dumps & json.loads
json.dumps() (dump string) serializa um objeto Python para uma string JSON; json.loads() (load string) faz parse de JSON de volta. Use indent para legibilidade, ensure_ascii=False para manter caracteres Unicode legíveis, e sort_keys para saída determinística. Chaves JSON devem ser strings — chaves int se tornam strings.
import json
# Serialize Python object to JSON string
data = {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
json_str = json.dumps(data)
print(json_str) # {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
# Pretty-print with indent
print(json.dumps(data, indent=2))
# {
# "name": "Alice",
# "age": 30,
# "scores": [95, 88, 92]
# }
# Parse JSON string to Python object
parsed = json.loads(json_str)
print(parsed["name"]) # Alice
print(type(parsed["scores"])) # <class 'list'>
# Sort keys, handle non-ASCII
print(json.dumps({"name": "Zoë"}, ensure_ascii=False, sort_keys=True))Lendo & Escrevendo Arquivos JSON
json.dump() escreve diretamente para um objeto de arquivo; json.load() lê de um. Sempre especifique encoding='utf-8' para portabilidade. Lembre-se do mapeamento de tipos: objetos JSON se tornam dicts, arrays se tornam lists, e números se tornam int ou float. Datetimes, sets e objetos personalizados NÃO são serializáveis em JSON por padrão.
import json
data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
# Write to file
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Read from file
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["users"][0]["name"]) # Alice
# Type conversions to remember:
# JSON object <-> Python dict
# JSON array <-> Python list
# JSON string <-> Python str
# JSON number <-> Python int/float
# JSON true/false <-> Python True/False
# JSON null <-> Python NoneCodificação JSON Personalizada (datetime, objetos personalizados)
O módulo json não pode serializar datetime, set ou classes personalizadas por padrão. Forneça uma função default (chamada para objetos não serializáveis) ou uma subclasse JSONEncoder. Para round-tripping, pareie um encoder personalizado com um object_hook em loads() para reconstruir os tipos originais. É assim que ORMs serializam objetos de modelo.
import json
from datetime import datetime
# Default behavior: TypeError on non-serializable types
# json.dumps({"now": datetime.now()}) # TypeError!
# Solution 1: default function for unknown types
def default_encoder(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return sorted(obj)
raise TypeError(f"Cannot serialize {type(obj)}")
data = {"now": datetime.now(), "tags": {"a", "b"}}
print(json.dumps(data, default=default_encoder))
# Solution 2: custom JSONEncoder subclass
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return {"__datetime__": obj.isoformat()}
return super().default(obj)
print(json.dumps(data, cls=MyEncoder))
# Decoding with object_hook
def decoder(dct):
if "__datetime__" in dct:
return datetime.fromisoformat(dct["__datetime__"])
return dct
json.loads(json.dumps(data, cls=MyEncoder), object_hook=decoder)Lendo Arquivos CSV
Sempre abra arquivos CSV com newline='' para evitar problemas de linhas em branco no Windows. csv.reader retorna lists; csv.DictReader retorna dicts indexados pela linha de cabeçalho. O módulo csv lida com quoting, vírgulas incorporadas e novas linhas corretamente — nunca divida linhas CSV manualmente com line.split(','). Use Sniffer para auto-detectar delimitadores.
import csv
# Basic reader: each row is a list of strings
with open("data.csv", newline="", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row) # ['name', 'age', 'city']
# DictReader: each row is a dict keyed by header
with open("data.csv", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"]) # access by column name
# Handle different delimiters and quoting
with open("data.tsv", newline="") as f:
reader = csv.reader(f, delimiter="\t", quotechar='"')
for row in reader:
print(row)
# Sniffer to auto-detect format
with open("unknown.csv", newline="") as f:
sample = f.read(1024)
dialect = csv.Sniffer().sniff(sample)
f.seek(0)
reader = csv.reader(f, dialect)Escrevendo Arquivos CSV
csv.writer escreve lists; csv.DictWriter escreve dicts com um conjunto fixo de fieldnames. Sempre use newline='' ao abrir o arquivo. O parâmetro quoting controla quando campos são citados — QUOTE_MINIMAL (padrão) cita apenas quando necessário, QUOTE_ALL cita tudo, útil para parsers estritos.
import csv
rows = [
["name", "age", "city"],
["Alice", 30, "NYC"],
["Bob", 25, "LA"],
]
# Basic writer
with open("out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(rows) # write multiple rows
# DictWriter: write from dicts
with open("out.csv", "w", newline="") as f:
fieldnames = ["name", "age", "city"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({"name": "Alice", "age": 30, "city": "NYC"})
writer.writerow({"name": "Bob", "age": 25, "city": "LA"})
# Control quoting: QUOTE_MINIMAL (default), QUOTE_ALL, QUOTE_NONNUMERIC
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
# QUOTE_ALL wraps every field in quotes: "Alice","30","NYC"JSON Lines (NDJSON) & Streaming
JSON Lines (NDJSON) coloca um objeto JSON por linha — ideal para logs, event streams e arquivos append-only porque você pode processar cada linha independentemente. Para documentos JSON únicos enormes, use a biblioteca ijson para stream-parse sem carregar o arquivo inteiro na memória. NDJSON é o padrão para muitos pipelines de dados.
import json
# JSON Lines: one JSON object per line (great for logs, big data)
records = [{"id": 1, "msg": "first"}, {"id": 2, "msg": "second"}]
# Write NDJSON
with open("logs.jsonl", "w") as f:
for rec in records:
f.write(json.dumps(rec) + "\n")
# Read NDJSON line by line (memory-efficient for huge files)
with open("logs.jsonl", "r") as f:
for line in f:
rec = json.loads(line)
print(rec["id"], rec["msg"])
# Stream large JSON arrays without loading everything into memory
# Use ijson library for streaming parsing of huge JSON files:
# import ijson
# with open("huge.json", "rb") as f:
# for item in ijson.items(f, "items.item"):
# process(item) # one item at a timeLogging & Testes
Básico do logging
O módulo logging é a forma padrão de emitir saída de diagnóstico — muito melhor que print() porque você controla níveis, formatos e destinos. Use logging.getLogger(__name__) por módulo para poder ajustar verbosidade por módulo. logging.exception() inclui automaticamente o traceback. Configure basicConfig uma vez na inicialização.
import logging
# Basic configuration (call once at program start)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Log levels (severity ascending)
logging.debug("Detailed debug info") # DEBUG (10)
logging.info("General information") # INFO (20)
logging.warning("Something unexpected") # WARNING (30)
logging.error("A real error occurred") # ERROR (40)
logging.critical("System is down") # CRITICAL (50)
# Logging exceptions with traceback
try:
1 / 0
except ZeroDivisionError:
logging.exception("Division failed") # includes full traceback
# Get a named logger (best practice per module)
logger = logging.getLogger(__name__)
logger.info("Module-specific log")Logging para Arquivo & Múltiplos Handlers
Handlers roteiam registros de log para destinos — console, arquivos, rede, email. RotatingFileHandler limita o tamanho do arquivo e mantém backups, prevenindo crescimento ilimitado de logs. Cada handler pode ter seu próprio nível e formato (ex.: logs detalhados para arquivo, logs concisos para console). TimedRotatingFileHandler rotaciona por tempo em vez de tamanho.
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Console handler (INFO and above)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
# Rotating file handler (DEBUG and above, max 5MB x 3 backups)
file_handler = RotatingFileHandler(
"app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.debug("debug to file only")
logger.info("info to both console and file")
logger.error("error everywhere")Básico do unittest
unittest é o framework de testes integrado do Python (estilo xUnit). Testes vivem em classes que herdam de TestCase. setUp/tearDown executam antes/depois de cada teste para isolamento. Asserções comuns: assertEqual, assertTrue, assertRaises, assertIn. Execute com python -m unittest para auto-descoberta de arquivos test_*.py.
import unittest
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
class TestMath(unittest.TestCase):
def setUp(self):
# runs before each test method
self.data = [1, 2, 3]
def tearDown(self):
# runs after each test method
pass
def test_add(self):
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 1), 0)
def test_add_types(self):
self.assertEqual(add("a", "b"), "ab")
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(1, 0)
def test_membership(self):
self.assertIn(2, self.data)
self.assertTrue(3 in self.data)
if __name__ == "__main__":
unittest.main()
# Run: python -m unittest test_file.py -vBásico do pytest
pytest é a ferramenta de teste Python mais popular — instruções assert simples dão relatórios de falha ricos, sem classes boilerplate. pytest.raises verifica exceções com correspondência regex opcional. pytest.approx lida com imprecisão de comparação de float. Instale com pip install pytest e execute com pytest -v para saída verbosa.
# test_math.py — pytest is simpler and more powerful than unittest
# Install: pip install pytest
# Run: pytest -v
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Plain functions, no classes required
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
def test_add_strings():
assert add("hello", " world") == "hello world"
# Testing exceptions with pytest.raises
import pytest
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(1, 0)
# Approximate float comparison
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)Fixtures do pytest
Fixtures são a injeção de dependência do pytest — elas fornecem dados de setup, objetos mock ou recursos para testes via nomes de parâmetros. Fixtures baseadas em yield lidam tanto com setup (antes do yield) quanto teardown (depois do yield). Scopes controlam reutilização: 'session' cria a fixture uma vez para toda a execução, 'module' uma vez por arquivo, 'function' (padrão) uma vez por teste.
import pytest
# A fixture provides setup data/resources to tests
@pytest.fixture
def sample_list():
return [1, 2, 3, 4, 5]
# Use fixtures by passing their name as a parameter
def test_length(sample_list):
assert len(sample_list) == 5
def test_sum(sample_list):
assert sum(sample_list) == 15
# Fixture with setup AND teardown (yield)
@pytest.fixture
def db_connection():
print("\n[setup] connecting to DB")
conn = {"connected": True}
yield conn # test runs here; value passed to test
print("\n[teardown] closing DB")
conn["connected"] = False
def test_db(db_connection):
assert db_connection["connected"] is True
# Fixture scopes: function (default), class, module, session
@pytest.fixture(scope="session")
def expensive_resource():
return load_large_dataset() # created once per test sessionparametrize & mocking no pytest
parametrize executa uma única função de teste em múltiplos conjuntos de entrada — elimina código de teste copy-paste e dá saída clara por caso. unittest.mock.patch substitui funções/objetos por mocks para testes isolados. assert_called_once_with verifica se o mock foi usado corretamente. @pytest.mark.skip e xfail lidam com testes incompletos de forma elegante.
import pytest
from unittest.mock import patch, MagicMock
# parametrize: run one test with multiple inputs
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
(100, 200, 300),
])
def test_add_many(a, b, expected):
assert add(a, b) == expected
# parametrize with IDs for readable output
@pytest.mark.parametrize("x", [1, 2, 3], ids=["one", "two", "three"])
def test_ids(x):
assert x > 0
# Mocking: replace external dependencies
def fetch_user(uid):
# imagine this calls a real API
return {"id": uid, "name": "real_user"}
@patch("__main__.fetch_user")
def test_with_mock(mock_fetch):
mock_fetch.return_value = {"id": 1, "name": "mocked"}
result = fetch_user(1)
assert result["name"] == "mocked"
mock_fetch.assert_called_once_with(1)
# Skip and expected failure
@pytest.mark.skip(reason="not implemented yet")
def test_future():
pass
@pytest.mark.xfail(reason="known bug #42")
def test_known_bug():
assert 1 == 2Programação de Rede
Servidor TCP
Cria um servidor TCP usando o módulo socket. bind associa o socket a um endereço, listen define a fila de backlog, accept bloqueia até um cliente conectar. Sempre feche conexões para liberar descritores de arquivo.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
conn.sendall(b'Hello')
conn.close()Cliente TCP
Cria um cliente TCP que se conecta a um servidor. connect estabelece a conexão, sendall envia todos os bytes, recv lê até o número especificado de bytes. Use encode/decode para conversão string-para-bytes.
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 8080))
client.sendall(b'Hello Server')
response = client.recv(1024)
print(response.decode())
client.close()Socket UDP
UDP é connectionless: sem handshake, sem entrega garantida. recvfrom retorna tanto dados quanto endereço do remetente. Use SOCK_DGRAM para UDP. Ideal para DNS, jogos e streaming em tempo real.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('localhost', 9090))
data, addr = sock.recvfrom(1024)
print(f"From {addr}: {data.decode()}")
sock.sendto(b'Reply', addr)Servidor HTTP
O módulo http.server fornece um servidor HTTP simples. Subclasse BaseHTTPRequestHandler e sobrescreva do_GET, do_POST. Use apenas para desenvolvimento; use gunicorn para produção.
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'<h1>Hello</h1>')
HTTPServer(('localhost', 8000), Handler).serve_forever()Timeout de Socket
settimeout define um timeout para todas as operações de socket. Se uma operação exceder o timeout, uma exceção socket.timeout é levantada. Use try/finally para garantir limpeza.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
sock.connect(('example.com', 80))
data = sock.recv(1024)
except socket.timeout:
print('Connection timed out')
finally:
sock.close()Banco de Dados (SQLite)
Criar Tabela
sqlite3 é integrado ao Python. connect cria ou abre um arquivo de banco de dados. CREATE TABLE IF NOT EXISTS previne erros se a tabela existir. Sempre chame commit para salvar alterações.
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER)''')
conn.commit()Inserir Dados
Sempre use queries parametrizadas (placeholders ?) para prevenir SQL injection. lastrowid retorna o ID auto-incrementado. Nunca use formatação de string para valores SQL.
cursor.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
('Alice', '[email protected]', 30))
conn.commit()
print(f"ID: {cursor.lastrowid}")Consultar Dados
fetchall retorna todas as linhas correspondentes como uma lista de tuplas. fetchone retorna uma única linha ou None. Para grandes conjuntos de resultados, itere sobre o cursor diretamente.
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
rows = cursor.fetchall()
for row in rows:
print(row)
cursor.execute('SELECT * FROM users WHERE id = ?', (1,))
user = cursor.fetchone()Atualizar & Deletar
UPDATE modifica linhas existentes, DELETE remove-as. rowcount indica linhas afetadas. Sempre use WHERE com DELETE. commit persiste alterações.
cursor.execute('UPDATE users SET age = ? WHERE name = ?', (31, 'Alice'))
cursor.execute('DELETE FROM users WHERE age < ?', (18,))
conn.commit()
print(f"Affected: {cursor.rowcount} rows")Row Factory
Usar conn como context manager faz auto-commit em caso de sucesso e rollback em caso de exceção. row_factory = sqlite3.Row permite acessar colunas por nome.
conn = sqlite3.connect('example.db')
conn.row_factory = sqlite3.Row
with conn:
conn.execute('INSERT INTO users (name, email) VALUES (?, ?)',
('Bob', '[email protected]'))
for row in conn.execute('SELECT * FROM users'):
print(row['name'], row['email'])Web Scraping
Básico do BeautifulSoup
requests busca conteúdo HTML, BeautifulSoup faz parse. html.parser é integrado; lxml é mais rápido. Sempre verifique response.status_code antes de fazer parse.
import requests
from bs4 import BeautifulSoup
resp = requests.get('https://example.com')
soup = BeautifulSoup(resp.text, 'html.parser')
print(soup.title.string)
print(soup.find('h1').text)Encontrar Elementos
find_all retorna todos os elementos correspondentes, find retorna o primeiro. Use class_ (com underscore). select usa seletores CSS para queries complexas.
links = soup.find_all('a')
for link in links:
print(link.get('href'), link.text)
article = soup.find('div', class_='article')
items = soup.select('ul.list > li.item')Extrair Tabelas
Tabelas são estruturadas como tr (linhas) contendo células td (dados) ou th (cabeçalho). strip remove espaços em branco. find_all aceita uma lista de nomes de tags.
table = soup.find('table')
for row in table.find_all('tr'):
cols = row.find_all(['td', 'th'])
data = [col.text.strip() for col in cols]
print(data)Lidar com Paginação
Paginação é tratada seguindo links de próxima página. select_one retorna a primeira correspondência ou None. Adicione time.sleep entre requisições.
all_items = []
url = 'https://example.com/page/1'
while url:
resp = requests.get(url)
soup = BeautifulSoup(resp.text, 'html.parser')
all_items.extend([i.text for i in soup.select('.item')])
next_link = soup.select_one('a.next')
url = next_link.get('href') if next_link else NoneSalvar para CSV
csv.DictWriter escreve dicionários para CSV. newline previne linhas em branco extras no Windows. encoding=utf-8 lida com caracteres especiais.
import csv
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['name', 'price'])
writer.writeheader()
for item in scraped_data:
writer.writerow(item)Web Async (aiohttp)
Cliente HTTP
aiohttp fornece HTTP assíncrono. ClientSession gerencia pooling de conexões. async with garante limpeza. asyncio.run executa a coroutine.
import aiohttp, asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
data = asyncio.run(fetch('https://api.example.com'))Requisições Concorrentes
asyncio.gather executa coroutines concorrentemente, reduzindo o tempo total. Todas as requisições compartilham a mesma sessão. Use um semáforo para limitar concorrência.
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [await r.text() for r in responses]Servidor Web
aiohttp.web cria servidores web assíncronos. Rotas são definidas com método HTTP e padrão de caminho. match_info extrai parâmetros de caminho.
from aiohttp import web
async def handle(request):
name = request.match_info.get('name', 'World')
return web.json_response({'message': f'Hello, {name}!'})
app = web.Application()
app.add_routes([web.get('/', handle), web.get('/{name}', handle)])
web.run_app(app, port=8080)Servidor WebSocket
WebSockets habilitam comunicação bidirecional em tempo real. WebSocketResponse lida com o handshake de upgrade. async for itera sobre mensagens.
async def ws_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
await ws.send_str(f'Echo: {msg.data}')
return wsSessão com Cookies
ClientSession persiste cookies entre requisições automaticamente. Essencial para scraping autenticado. Use uma única sessão para todas as requisições.
async def login_and_fetch():
async with aiohttp.ClientSession() as session:
await session.post('https://example.com/login',
data={'user': 'admin', 'pass': '123'})
resp = await session.get('https://example.com/dashboard')
return await resp.text()Aprofundamento em Multiprocessing
Pool de Processos
Pool gerencia processos worker. map distribui trabalho em paralelo. apply_async executa uma única função assincronamente. Sempre use o guard if __name__ == main no Windows.
from multiprocessing import Pool
def square(x): return x * x
if __name__ == '__main__':
with Pool(4) as pool:
results = pool.map(square, range(10))
result = pool.apply_async(square, (100,))
print(result.get(timeout=5))Memória Compartilhada
Value e Array criam memória compartilhada entre processos. Use get_lock para sincronizar acesso e prevenir race conditions.
from multiprocessing import Value, Array
counter = Value('i', 0)
arr = Array('d', [0.0, 1.0, 2.0])
with counter.get_lock():
counter.value += 1Comunicação por Queue
Queue habilita comunicação segura entre processos. put adiciona itens, get recupera-os. Queue é process-safe, lidando com locking internamente.
from multiprocessing import Process, Queue
def worker(q):
q.put('Data from worker')
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get())
p.join()Pipe
Pipe cria um canal de comunicação bidirecional. send e recv transmitem objetos Python via pickling. Pipe é mais rápido que Queue para comunicação ponto a ponto.
from multiprocessing import Process, Pipe
def worker(conn):
conn.send(['hello', 'world'])
msg = conn.recv()
conn.close()
if __name__ == '__main__':
parent, child = Pipe()
p = Process(target=worker, args=(child,))
p.start()
print(parent.recv())
parent.send('acknowledged')
p.join()Sincronização
Lock garante que apenas um processo acesse um recurso compartilhado por vez. with lock adquire e libera automaticamente. Outras primitivas: RLock, Semaphore, Event.
from multiprocessing import Process, Lock
def safe_print(lock, msg):
with lock:
print(msg)
if __name__ == '__main__':
lock = Lock()
procs = [Process(target=safe_print, args=(lock, f'Task {i}'))
for i in range(5)]
for p in procs: p.start()
for p in procs: p.join()Aprofundamento em Ambientes Virtuais
Módulo venv
venv cria ambientes Python isolados com seus próprios diretórios de pacotes. A ativação modifica o PATH. Sempre ative antes de instalar dependências.
# Create
python -m venv myenv
# Activate (Linux/Mac)
source myenv/bin/activate
# Activate (Windows)
myenv\Scripts\activate
# Deactivate
deactivaterequirements.txt
requirements.txt lista dependências do projeto. == fixa versões exatas, >= permite upgrades dentro de um intervalo. Sempre faça commit ao controle de versão.
# Generate
pip freeze > requirements.txt
# Install
pip install -r requirements.txt
# Pin versions
flask==2.3.3
requests>=2.28.0,<3.0.0Poetry
Poetry é um gerenciador de dependências moderno. pyproject.toml substitui requirements.txt. Ambientes virtuais são gerenciados automaticamente.
# Initialize
poetry init
# Add dependency
poetry add flask
poetry add pytest --group dev
# Install all
poetry install
# Run command
poetry run python app.pypipenv
pipenv combina pip e virtualenv. Pipfile declara dependências, Pipfile.lock fixa versões exatas. --dev separa dependências de desenvolvimento.
# Create environment
pipenv install
# Add package
pipenv install requests
pipenv install pytest --dev
# Activate shell
pipenv shell
# Run command
pipenv run python app.pyAmbientes Conda
Conda gerencia tanto dependências Python quanto não-Python. environment.yml captura o ambiente completo. Ideal para ciência de dados com dependências binárias.
# Create
conda create -n myenv python=3.11
# Activate
conda activate myenv
# Export
conda env export > environment.yml
# Recreate
conda env create -f environment.ymlUso Avançado do pip
Instalar do Git
Instale pacotes diretamente de repositórios Git. Útil para versões não lançadas, forks ou pacotes privados. @branch ou @commit fixa para uma versão específica.
# From GitHub
pip install git+https://github.com/user/repo.git
# Specific branch
pip install git+https://github.com/user/repo.git@branch-name
# Specific commit
pip install git+https://github.com/user/repo.git@abc123Instalação Editável
Instalação editável (-e) vincula o pacote em vez de copiar. Alterações ficam imediatamente disponíveis sem reinstalação. Essencial para desenvolvimento de pacotes.
# Install in development mode
pip install -e .
# From a specific path
pip install -e /path/to/package
# With extras
pip install -e ".[dev,test]"Constraints & Hashes
Constraints limitam quais versões podem ser instaladas. Verificação de hash verifica integridade do pacote, prevenindo ataques de cadeia de suprimentos.
# constraints.txt
flask==2.3.3
pip install -c constraints.txt flask
# Hash checking
pip install --require-hashes -r requirements.txtGerenciamento de Cache
pip armazena em cache wheels baixados. --no-cache-dir força downloads frescos. Purge libera espaço em disco quando o cache cresce demais.
# Show cache info
pip cache info
# List cached packages
pip cache list
# Purge entire cache
pip cache purge
# Install with no cache
pip install --no-cache-dir flaskÍndice Personalizado
--index-url especifica um repositório de pacotes personalizado. --extra-index-url adiciona um fallback. --trusted-host ignora SSL para registros internos.
# Use custom index
pip install --index-url https://pypi.custom.com/simple/ flask
# Extra index (fallback)
pip install --extra-index-url https://pypi.custom.com/simple/ flask
# Trusted host (no SSL)
pip install --trusted-host pypi.custom.com flaskVerificação de Tipos (mypy)
Type Hints Básicos
Type hints anotam parâmetros de função e tipos de retorno. Python 3.9+ permite tipos integrados diretamente. Hints habilitam análise estática com mypy.
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
def process(data: list[int]) -> dict[str, int]:
return {str(x): x for x in data}Optional e Union
Optional[X] é equivalente a X | None (Python 3.10+). Tipos Union permitem múltiplos tipos possíveis. mypy verifica se todos os caminhos de código tratam todos os tipos.
from typing import Optional
def find(items: list[int], target: int) -> int | None:
for i, v in enumerate(items):
if v == target: return i
return NoneTipos Genéricos
Generics criam contêineres type-safe reutilizáveis. TypeVar define uma variável de tipo, Generic torna a classe genérica. mypy garante consistência de tipo.
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()Protocol
Protocol define subtipagem estrutural (duck typing com verificação de tipos). Qualquer classe com os métodos necessários satisfaz o protocolo, sem herança necessária.
from typing import Protocol
class Closeable(Protocol):
def close(self) -> None: ...
def cleanup(resource: Closeable) -> None:
resource.close()
class File:
def close(self) -> None: print("Closed")
cleanup(File()) # OK - File has close()Configuração do mypy
mypy.ini configura a rigidez da verificação de tipos. strict habilita todas as verificações. Overrides por módulo relaxam regras para testes ou código legado.
# mypy.ini
[mypy]
python_version = 3.11
strict = True
warn_return_any = True
disallow_untyped_defs = True
[mypy-tests.*]
ignore_errors = TrueDicas de Performance
Lista vs Gerador
Listas armazenam todos os elementos na memória; geradores produzem valores sob demanda. Use geradores para sequências grandes iteradas uma vez.
# List: all in memory
squares = [x**2 for x in range(1000000)]
# Generator: lazy evaluation
squares_gen = (x**2 for x in range(1000000))
import sys
print(sys.getsizeof(squares)) # ~8MB
print(sys.getsizeof(squares_gen)) # ~200 bytesConcatenação de Strings
Concatenação de strings com += é O(n^2). join é O(n). f-strings são o método de interpolação mais rápido.
# Slow: creates intermediate strings
result = ""
for s in parts:
result += s
# Fast: join in one operation
result = "".join(parts)
# Fast: f-strings
msg = f"Hello, {name}!"Variáveis Locais
Lookups de variáveis locais são mais rápidos que lookups globais ou de atributos. Atribuir funções frequentemente usadas a locais acelera loops.
import math
# Slow: global lookup
def compute_slow(values):
return [math.sqrt(v) for v in values]
# Fast: local reference
def compute_fast(values):
sqrt = math.sqrt
return [sqrt(v) for v in values]__slots__
__slots__ previne criação de __dict__, economizando 40-50% de memória por instância. Significativo ao criar milhões de objetos. Não pode adicionar atributos não listados.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeErrortimeit & cProfile
timeit mede tempo de execução de pequenos snippets. cProfile mostra onde o tempo é gasto. Use profiling antes de otimizar para encontrar gargalos reais.
import timeit
t = timeit.timeit('sum(range(100))', number=10000)
print(f"{t:.4f}s")
import cProfile
cProfile.run('sum(x**2 for x in range(10000))')Armadilhas Comuns
Args Padrão Mutáveis
Valores de argumentos padrão são avaliados uma vez no momento da definição. Defaults mutáveis são compartilhados entre todas as chamadas. Sempre use None como default.
# BUG: default list is shared
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2]!
# FIX: use None
def add_item(item, lst=None):
if lst is None: lst = []
lst.append(item)
return lstClosures com Late Binding
Closures capturam variáveis por referência. No momento em que lambdas são chamadas, a variável do loop tem seu valor final. Argumentos padrão capturam o valor atual.
# BUG: all print 2
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]
# FIX: default argument
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]Cache de Inteiros
Python armazena em cache inteiros pequenos. is verifica identidade, == verifica igualdade. Nunca use is para comparação de valores; use is apenas para None, True, False.
# Small integers cached (-5 to 256)
a = 256; b = 256
print(a is b) # True (cached)
c = 257; d = 257
print(c is d) # False (not cached)
print(c == d) # Trueis vs ==
is verifica se duas referências apontam para o mesmo objeto. == verifica se dois objetos têm o mesmo valor. Use is apenas para None, True, False.
a = [1, 2, 3]; b = [1, 2, 3]
print(a == b) # True (same values)
print(a is b) # False (different objects)
# Correct usage of is
if x is None: ...
if x is not None: ...GIL
O GIL permite que apenas uma thread execute bytecode Python por vez. Threading é eficaz para tarefas I/O-bound. Use multiprocessing para paralelismo CPU-bound.
# GIL prevents true parallelism for CPU-bound tasks
import threading
def cpu_work():
total = sum(i**2 for i in range(10**7))
# Use multiprocessing for CPU work
from multiprocessing import Pool
with Pool(4) as p:
p.map(cpu_work, range(4))Snippets de Python relacionados
Copy-paste ready code for common tasks.
Ordenar Dicionário por Valor
Ordenar um dicionário Python por seus valores em ordem decrescente.
Compreensão de Lista
Gerar listas rapidamente usando compreensões de lista.
Merge de Dicionários
Múltiplas maneiras de mesclar dicionários.
Leitura/Escrita de Arquivo
Várias maneiras de ler e escrever arquivos.
Processamento de CSV
Ler e escrever arquivos CSV usando o módulo csv.
Processamento de JSON
Serialização e desserialização JSON.
Correspondência de Regex
Realizar correspondência de regex usando o módulo re.
Tratamento de Datas
Tratar datas e horas com datetime.
Decoradores
Definir e usar decoradores.
Geradores
Economizar memória usando geradores.
Context Manager
Context managers personalizados.
Tratamento de Exceções
Mecanismo completo de tratamento de exceções.
Herança de Classe
Herança de classe e sobrescrita de métodos.
Multithreading
Implementar multithreading usando o módulo threading.
Multiprocessing
Alcançar paralelismo real com multiprocessing.
Programação Assíncrona com asyncio
Implementar concorrência assíncrona com asyncio.
Programação com Socket
Servidor e cliente TCP Socket.
Requisições HTTP
Enviar requisições HTTP usando a biblioteca requests.
Operações de Banco de Dados
Operar em bancos de dados usando sqlite3.
Ambiente Virtual
Criar e gerenciar ambientes virtuais Python.
Instalação com pip
Comandos comuns de gerenciamento de pacotes pip.
Variáveis de Ambiente
Ler e definir variáveis de ambiente.
Logging
Configurar e usar o módulo logging.
Testes Unitários
Escrever testes unitários usando unittest.
Type Hints
Melhorar a legibilidade do código com anotações de tipo.
Dataclass
Simplificar definições de classe com dataclass.
Enum
Definir tipos enum usando Enum.
Decorador property
Controlar acesso a atributos com property.
Métodos Mágicos
Exemplos comuns de métodos mágicos.
Iterador
Implementação de iterador personalizado.
Corrotina
Uso básico de corrotinas.
Was this helpful?