[ ]
Listas & Comprehensions
# Filtrar + transformar en una línea
evens = [x**2 for x in range(20) if x % 2 == 0]

# Aplanar lista de listas
flat = [x for sub in matrix for x in sub]

# Dict comprehension
inv = {v: k for k, v in original.items()}

# Set comprehension (elimina duplicados)
uniq = {x.lower() for x in words}

# Generator (sin cargar todo en memoria)
total = sum(x**2 for x in range(1_000_000))
Usa generators cuando el resultado es un agregado (sum, max, any). Usa list cuando necesitas indexar o reutilizar.
fn
Built-ins clave
# zip — iterar en paralelo
for name, score in zip(names, scores):
    print(f"{name}: {score}")

# enumerate — índice + valor
for i, item in enumerate(items, start=1):
    print(f"{i}. {item}")

# sorted con clave personalizada
top = sorted(users, key=lambda u: u.score, reverse=True)[:10]

# map + filter (prefer comprehensions)
clean = list(filter(None, map(str.strip, lines)))

# any / all — cortocircuito
has_admin = any(u.role == 'admin' for u in users)
all_valid = all(v > 0 for v in values)
{ }
Dicts — operaciones útiles
# Merge dicts (Python 3.9+)
merged = base | overrides

# get con default — evita KeyError
val = data.get('key', 0)

# setdefault — init si no existe
groups.setdefault(key, []).append(item)

# Counter — frecuencias
from collections import Counter
freq = Counter(words)
top3 = freq.most_common(3)

# defaultdict — no más KeyError al agregar
from collections import defaultdict
graph = defaultdict(list)
graph[node].append(neighbor)
str
Strings — manipulación
# f-strings avanzados
print(f"{value:.2f}")      # 2 decimales
print(f"{n:,}")           # separador de miles
print(f"{name!r}")        # repr()
print(f"{x = }")          # debug: "x = 42"

# split / join — patrón común
parts = text.split(',')
result = ', '.join(parts)

# strip / replace en cadena
clean = raw.strip().lower().replace('-', '_')

# Verificaciones útiles
'abc'.startswith(('a', 'b'))
'hello'.isalpha()
'123'.isdigit()
IO
Archivos & Paths
from pathlib import Path

p = Path('data/report.csv')
p.exists(), p.suffix, p.stem   # .csv, report
p.parent / 'output.json'       # data/output.json

# Leer todo / línea a línea
text = p.read_text(encoding='utf-8')
lines = p.read_text().splitlines()

# Listar archivos recursivo
csvs = list(Path('.').glob('**/*.csv'))

# Crear directorio si no existe
Path('output/logs').mkdir(parents=True, exist_ok=True)
Prefiere pathlib sobre os.path. Es orientado a objetos, más legible y multiplataforma.
itertools — power tools
from itertools import (
    chain, islice, groupby,
    product, combinations, batched  # 3.12+
)

# Concatenar iterables sin crear lista
all_items = chain(list1, list2, list3)

# Tomar N primeros de generator
first10 = list(islice(infinite_gen, 10))

# Agrupar por clave (requiere sort previo)
data.sort(key=lambda x: x['dept'])
for dept, group in groupby(data, key=lambda x: x['dept']):
    ...

# Procesar en lotes (batched Python 3.12+)
for batch in batched(records, 100):
    db.insert_many(batch)
KeyError dict / set
Acceso a clave que no existe en un dict.
# ❌ Peligroso
val = data['user']['email']

# ✅ Seguro con get()
val = data.get('user', {}).get('email', '')

# ✅ Con default explícito
val = data.get('missing_key', 'N/A')
Usa .get() siempre que la clave sea opcional. Usa [] solo cuando ausencia = bug real.
AttributeError: 'NoneType' has no attribute None
Llamar métodos sobre un valor que puede ser None.
# ❌ Clásico
result = get_user(id).name.upper()

# ✅ Guard clause
user = get_user(id)
if user is None:
    return 'Unknown'
return user.name.upper()

# ✅ Walrus operator (Python 3.8+)
if (user := get_user(id)) is not None:
    print(user.name)
Funciones que "buscan" algo deben retornar None o lanzar excepción, nunca valores mágicos.
Mutable default argument silencioso
Lista o dict como valor default se comparte entre llamadas.
# ❌ Bug clásico de Python
def add_item(item, lst=[]):
    lst.append(item)
    return lst
# add_item(1) → [1]
# add_item(2) → [1, 2]  ← BUG!

# ✅ Usar None como sentinel
def add_item(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
Regla: nunca uses [], {}, o set() como default. Usa None y crea dentro de la función.
UnboundLocalError scope
Variable local referenciada antes de asignarse; conflicto con global.
# ❌ Python asume que count es local
count = 0
def increment():
    count += 1   # UnboundLocalError!

# ✅ Pasar como parámetro (preferido)
def increment(count):
    return count + 1

# ✅ global (evitar si es posible)
def increment():
    global count
    count += 1
Evita global. Si necesitas estado compartido, usa una clase o closure.
RecursionError recursión
Recursión sin caso base o demasiado profunda.
# ❌ Sin caso base
def fib(n):
    return fib(n-1) + fib(n-2)

# ✅ Con memoization
from functools import lru_cache

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

# Aumentar límite si es necesario
import sys; sys.setrecursionlimit(10_000)
lru_cache convierte recursión exponencial en O(n). Úsalo en funciones puras con recursión.
Modificar lista mientras se itera silencioso
Resultados impredecibles al borrar/añadir elementos durante un for.
# ❌ Salta elementos
for item in my_list:
    if condition(item):
        my_list.remove(item)

# ✅ Mejor: filter comprehension
my_list = [x for x in my_list if not condition(x)]

# ✅ Iterar sobre copia
for item in my_list[:]:
    if condition(item):
        my_list.remove(item)
Siempre prefiere crear una nueva lista filtrada sobre modificar in-place durante iteración.
TypeError: unhashable type 'list' hash
Usar lista como clave de dict o elemento de set.
# ❌
seen = set()
seen.add([1, 2, 3])   # TypeError!

# ✅ Convertir a tuple (hashable)
seen.add((1, 2, 3))

# ✅ frozenset para sets de sets
seen.add(frozenset({1, 2, 3}))
Hashables: int, float, str, tuple, frozenset. No hashables: list, dict, set.
Excepción demasiado genérica bad practice
Capturar Exception oculta bugs reales y dificulta el debugging.
# ❌ Silencia todo
try:
    process(data)
except Exception:
    pass

# ✅ Específico + logging
try:
    result = parse_json(raw)
except (ValueError, KeyError) as e:
    logger.warning(f"Parse failed: {e}")
    result = None
except IOError:
    logger.error("File not accessible")
    raise
Captura solo las excepciones que sabes manejar. Deja que el resto burbujee.
Late binding en closures / lambdas silencioso
Variables de loop capturadas por referencia, no por valor.
# ❌ Todas usan i=9
funcs = [lambda: i for i in range(10)]
print(funcs[0]())  # → 9, no 0!

# ✅ Capturar por valor con default arg
funcs = [lambda i=i: i for i in range(10)]
print(funcs[0]())  # → 0 ✓

# ✅ Alternativa: functools.partial
from functools import partial
funcs = [partial(lambda x: x, i) for i in range(10)]
Si creas funciones dentro de loops, captura el valor con un default argument.
ctx
Context Manager
from contextlib import contextmanager
import time

@contextmanager
def timer(label=''):
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.3f}s")

# Uso
with timer('query'):
    results = db.fetch_all()
Ideal para: timers, conexiones, locks, transacciones, cambio temporal de estado.
dc
Dataclass — structs limpios
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass(frozen=True)  # immutable + hashable
class Point:
    x: float
    y: float
    label: str = ''

@dataclass
class Config:
    host: str = 'localhost'
    port: int = 8080
    tags: list = field(default_factory=list)
    _count: ClassVar[int] = 0

points = {Point(0,0), Point(1,2)}  # frozen → set ok
Usa field(default_factory=list) en lugar de tags=[] para evitar el bug de mutable default.
ty
Type hints — producción
from typing import TypeVar, Protocol
from collections.abc import Callable, Iterator

T = TypeVar('T')

def first(items: list[T], default: T | None = None) -> T | None:
    return items[0] if items else default

# Protocol: duck typing + type safety
class Drawable(Protocol):
    def draw(self) -> None: ...

def render(obj: Drawable) -> None:
    obj.draw()

# TypeAlias (3.10+)
Vector = list[float]
Matrix = list[Vector]
log
Logging — no más print()
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
    datefmt='%H:%M:%S'
)
logger = logging.getLogger(__name__)

logger.debug("Detalle interno")
logger.info("Usuario %s autenticado", username)
logger.warning("Retry %d/%d", attempt, max_retries)
logger.error("Falla en %s", endpoint, exc_info=True)
exc_info=True agrega el traceback completo. Usa %s en lugar de f-strings para lazy formatting.
gen
Generators — pipelines eficientes
# Pipeline lazy sin cargar todo en memoria
def read_lines(path):
    with open(path) as f:
        yield from f

def parse(lines):
    for line in lines:
        yield line.strip().split(',')

def filter_valid(rows):
    yield from (r for r in rows if len(r) == 3)

# Componer: procesa una línea a la vez
pipeline = filter_valid(parse(read_lines('data.csv')))
for row in pipeline:
    process(row)
yield from delega a otro iterable. El pipeline es lazy: sin importar el tamaño del archivo.
dec
Decoradores — retry automático
import functools, time

def retry(times=3, delay=1):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return fn(*args, **kwargs)
                except Exception as e:
                    if attempt == times - 1: raise
                    time.sleep(delay * 2**attempt)
        return wrapper
    return decorator

@retry(times=3, delay=0.5)
def fetch_api(url): ...
*
Unpacking avanzado
# Star unpacking
first, *rest = [1, 2, 3, 4, 5]
*init, last  = [1, 2, 3, 4, 5]

# Swap sin variable temp
a, b = b, a

# Unpack nested
(x, y), z = (1, 2), 3

# Merge dicts
full = {**defaults, **user_config, **env_config}

# Pasar lista/dict como args
args = [1, 2, 3]
result = fn(*args, **{'key': 'val'})
match/case — Pattern Matching
# Python 3.10+ structural pattern matching
match command:
    case {"action": "move", "dir": direction}:
        move(direction)
    case {"action": "attack", "target": t} if t in enemies:
        attack(t)
    case [x, y]:           # lista de 2 elementos
        draw(x, y)
    case Point(x=0, y=0):  # class pattern
        print("origin")
    case _:                # default
        print("unknown")
Mucho más expresivo que if/elif anidados. Funciona con dicts, listas, clases y literales.
Guard clauses — reducir nesting
# ❌ Pyramid of doom
def process(user, data):
    if user:
        if user.is_active:
            if data:
                if validate(data):
                    return save(user, data)

# ✅ Early returns
def process(user, data):
    if not user:           return None
    if not user.is_active: raise ValueError("Inactive")
    if not data:           return None
    if not validate(data): raise ValueError("Invalid")
    return save(user, data)
Cada guard clause elimina un nivel de indentación. El camino feliz queda al final, limpio.
fn
Funciones — principios clave
Una función, una responsabilidad. Si el nombre necesita "and", dividir en dos.
Máximo 3–4 parámetros. Más → agrupar en dataclass o dict.
Funciones puras son testeables. Aisla los side effects al borde del sistema.
# Keyword-only args (después del *)
def create_user(name, email, *, role='user', notify=True):
    ...

# Llamada explícita — sin ambigüedad posicional
create_user('Ana', 'ana@x.com', role='admin', notify=False)
Nombrado — convenciones
# Variables: snake_case, descriptivos
user_count = 42           # no: uc, x, temp
is_authenticated = True   # bool: is_/has_/can_
MAX_RETRIES = 3           # constante: UPPER_CASE

# Prefijos especiales
_internal = 'privado módulo'
__dunder__ = 'protocolo Python'
_ = 'valor descartado'

# Funciones: verbo + sustantivo
get_user_by_id()
calculate_total_price()
is_valid_email()       # retorna bool
has_permission(user, 'admin')
Testing — patrones esenciales
import pytest
from unittest.mock import patch, MagicMock

# Parametrize — múltiples casos, un test
@pytest.mark.parametrize("inp,expected", [
    ("hello", "HELLO"),
    ("", ""),
    ("123", "123"),
])
def test_uppercase(inp, expected):
    assert to_upper(inp) == expected

# Mock de dependencia externa
@patch('mymodule.requests.get')
def test_fetch(mock_get):
    mock_get.return_value.json.return_value = {'id': 1}
    result = fetch_user(1)
    assert result['id'] == 1
Performance — tips rápidos
Lookups en set/dict son O(1). En list son O(n). Para membership checks, convierte a set primero.
join() para concatenar strings, nunca + en loop.
Slicing crea copias. Para leer sin copiar usa memoryview o itertools.islice.
# ❌ O(n) lookup por iteración
valid = [x for x in data if x in allowed_list]

# ✅ O(1) con set
allowed_set = set(allowed_list)
valid = [x for x in data if x in allowed_set]

# ✅ String join eficiente
result = ''.join(str(x) for x in items)
env
Configuración — no hardcoding
import os
from dataclasses import dataclass

@dataclass
class Settings:
    # Opcional con default
    db_url: str  = os.environ.get('DATABASE_URL', 'sqlite:///dev.db')
    debug: bool  = os.environ.get('DEBUG', 'false').lower() == 'true'
    workers: int = int(os.environ.get('MAX_WORKERS', 4))
    # Requerida: KeyError si falta → falla rápido
    api_key: str = os.environ['API_KEY']

settings = Settings()  # Singleton global
Variables requeridas con os.environ[] fallan inmediatamente al iniciar — falla rápido es bueno.