Referencia

Referencia de Python

Sintaxis, ejemplos y explicaciones breves: desde las bases del lenguaje hasta patrones algorítmicos, clases y pruebas.

Fundamentos del lenguaje13

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

El tipo se deduce del valor; no hay que declararlo. Comprueba con type(x) o isinstance(x, int).

f-strings
f"{name} solved {n} tasks"
f"{ratio:.2f}"
f"{n:>5}"
f"{n=}"

Interpola expresiones en una cadena. :.2f redondea a dos decimales, :>5 alinea, {n=} imprime nombre y valor.

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

Las colecciones vacías, 0, "" y None son falsos. Usa `if items` en vez de `if len(items) > 0`.

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

Intercambia sin variable temporal; *rest recoge el resto de la secuencia.

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

Condicional en una línea. Devuelve un valor, así que se puede asignar o retornar.

Floor division and modulo
7 // 2          # 3
7 % 2           # 1
divmod(7, 2)    # (3, 1)
-7 // 2         # -4

// redondea hacia abajo, no hacia cero: -7 // 2 es -4. divmod devuelve cociente y resto a la vez.

Length with len
len([10, 20, 30])  # 3

`len()` es una función incorporada que utiliza el protocolo `__len__`, por lo que funciona de forma uniforme con distintos contenedores. En Python no existe el método `.len()`.

Integer input
n = int(input())

`input()` siempre devuelve una cadena, incluso cuando el usuario introduce un número. `int()` lanza `ValueError` si la cadena no representa un entero válido.

Type conversion and integer base
s = str(42)
n = int("42")
x = float("3.5")
h = int("ff", 16)

`str`, `int` y `float` crean valores del tipo solicitado, mientras que el segundo argumento de `int()` indica la base numérica. Una cadena no válida provoca `ValueError`.

Type checks with isinstance
isinstance(True, int)  # True
type(True) is int  # False

`isinstance()` tiene en cuenta la herencia y normalmente es la mejor opción para comprobar un tipo. `type(x) is T` solo acepta una coincidencia exacta.

None as a default value
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

`None` es un marcador seguro cuando hay que crear un objeto mutable por separado en cada llamada. Debe comprobarse con `is None`, no con `== None`.

Identity versus equality
a = [1]
b = [1]
a == b  # True
a is b  # False
x = 1
y = 1
x is y  # True

`==` compara valores, mientras que `is` comprueba si dos referencias apuntan al mismo objeto. Los enteros pequeños pueden estar internados, por lo que `is` no debe usarse para comparar números.

Control de flujo10

If / elif / else
if score >= 90:
    grade = "A"
elif score >= 75:
    grade = "B"
else:
    grade = "C"

Las condiciones se comprueban de arriba abajo y se ejecuta la primera rama que coincida. Una condición general puede ocultar otra más específica situada después.

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

Una cadena de comparaciones funciona como varias comprobaciones unidas con and, pero la expresión intermedia se evalúa una sola vez. Con None se suele comparar mediante is o is not.

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

and y or usan evaluación de cortocircuito y devuelven uno de sus operandos, no necesariamente un bool. El operador not siempre devuelve un bool.

For and while loops
for item in items:
    process(item)

while left < right:
    left += 1

for recorre un iterable, mientras que while se repite mientras su condición sea verdadera. Un while debe modificar el estado relevante o puede no terminar nunca.

Range
range(stop)
range(start, stop)
range(start, stop, step)
range(n - 1, -1, -1)

range crea una secuencia perezosa de enteros y no incluye stop. Para recorrer hacia atrás se necesita un step negativo, y el paso cero no es válido.

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

break termina inmediatamente el bucle más cercano. En bucles anidados, el bucle exterior continúa.

Continue
for x in items:
    if x < 0:
        continue
    process(x)

continue omite el resto de la iteración actual y pasa a la siguiente. En un while, hay que actualizar antes las variables que controlan el bucle.

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

El bloque else de un bucle se ejecuta solo si el bucle termina sin break. También se ejecuta cuando el iterable está vacío.

Match / case
match token:
    case 0:
        kind = "zero"
    case int() if token > 0:
        kind = "positive"
    case _:
        kind = "other"

match selecciona el primer patrón que coincida y _ actúa como caso general. La ejecución no continúa automáticamente por los casos siguientes.

Unpacking in loops
for key, value in pairs:
    process(key, value)

for i, (x, y) in enumerate(points):
    process(i, x, y)

El destino del bucle puede desempaquetar directamente estructuras anidadas. El número y la forma de los valores deben coincidir salvo que se use un destino con asterisco.

Funciones10

Function and multiple return values
def bounds(values):
    return min(values), max(values)

low, high = bounds(items)

Varios valores devueltos se empaquetan realmente en una tupla. Al desempaquetarlos, el número de destinos debe coincidir con el de elementos.

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

El valor predeterminado se usa cuando se omite el argumento. La expresión predeterminada se evalúa una sola vez al crear la función.

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

Un objeto mutable predeterminado se comparte entre llamadas, por lo que los datos pueden acumularse de forma inesperada. Normalmente se usa None y se crea el objeto dentro de la función.

Variadic arguments
def collect(first, *args, **kwargs):
    return first, args, kwargs

*args reúne los argumentos posicionales adicionales en una tupla y **kwargs reúne los argumentos con nombre en un diccionario. Los parámetros normales se enlazan primero.

Keyword-only arguments
def clamp(value, *, low=0, high=100):
    return max(low, min(value, high))

Los parámetros situados después de * solo pueden pasarse por nombre. Esto evita errores causados por el orden poco claro de argumentos parecidos.

Lambda
key = lambda item: item[1]
result = sorted(items, key=key)

lambda crea una función pequeña a partir de una sola expresión y no admite instrucciones normales. Para una lógica más compleja resulta más claro usar def.

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

Un cierre conserva el acceso a los nombres de la función exterior después de que esta termine. Los nombres se capturan por referencia, lo que puede causar problemas de enlace tardío en bucles.

Global and nonlocal
count = 0

def outer():
    total = 0

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

    return update

global modifica un nombre del módulo y nonlocal un nombre del ámbito exterior más cercano que ya lo define. Sin estas declaraciones, una asignación crea un nombre local.

LRU cache
from functools import lru_cache

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

lru_cache guarda los resultados de las llamadas y resulta especialmente útil para subproblemas recursivos repetidos. Los argumentos deben ser hashables y maxsize=None crea una caché sin límite.

Type annotations
def binary_search(a: list[int], target: int) -> int | None:
    ...

Las anotaciones describen los tipos esperados de los parámetros y del resultado, pero por sí solas no los comprueban en tiempo de ejecución. X | None indica que el resultado puede estar ausente.

Cadenas7

String slicing
s[1:4]
s[:3]
s[-2:]
s[::-1]
s[::2]

El límite derecho no se incluye. s[::-1] invierte la cadena, s[::2] toma un carácter de cada dos, los índices negativos cuentan desde el final.

split / join
"a,b,c".split(",")     # ["a", "b", "c"]
"a b  c".split()
",".join(["a", "b"])   # "a,b"

split() sin argumento divide por espacios y descarta vacíos. join vuelve a unir las partes.

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

Las cadenas son inmutables: los métodos devuelven una nueva. find da -1 si no encuentra; index lanza error.

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

isdigit/isalpha/isalnum son verdaderos solo si la cadena no está vacía y coincide entera. isupper/islower comprueban todos los caracteres con mayúsculas y minúsculas: los dígitos y signos se ignoran, pero hace falta al menos uno de ellos. `in` comprueba si un carácter o subcadena está presente.

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

sorted(s) devuelve los caracteres ordenados, así que comparar dos de esas listas es la comprobación típica de anagrama. Un set no sirve: pierde cuántas veces se repite cada carácter. Ambas formas distinguen mayúsculas, espacios y signos.

Building a string
parts = []
for w in words:
    parts.append(w)
result = "".join(parts)

No construyas cadenas con += en un bucle: cada paso copia todo. Acumula en una lista y une al final.

ord / chr
ord('a')            # 97
chr(97)             # 'a'
ord(c) - ord('a')

Código del carácter y vuelta. ord(c) - ord("a") da el índice de la letra — típico para contadores de 26.

Cadenas: nivel avanzado10

Formatted strings
"{name}: {score:.2f}".format(name="Ada", score=9.876)
score = 9.876
f"{score:.2f}"

`str.format` sustituye valores en una plantilla, mientras que una f-string evalúa expresiones directamente en el ámbito actual. Los especificadores controlan la precisión, el signo y la representación, y las llaves literales se escriben como `{{` y `}}`.

String alignment and filling
x = "42"
f"{x:>8}"
f"{x:<8}"
f"{x:*^8}"

Los especificadores `<`, `>` y `^` alinean un valor a la izquierda, a la derecha o al centro, y el carácter anterior define el relleno. El ancho es mínimo y no recorta un valor demasiado largo.

Character translation table
table = str.maketrans({"a": "@", "e": "3"})
"peace".translate(table)

`str.maketrans` crea una tabla de transformación y `translate` la aplica a todos los caracteres en una sola pasada. Un valor `None` en la tabla elimina el carácter correspondiente.

String partition
head, sep, tail = "key=value".partition("=")

`partition` divide solo por la primera aparición y siempre devuelve tres elementos, incluido el propio separador. Si no encuentra el separador, la cadena original queda en el primer elemento y los otros dos están vacíos.

Split text into lines
"a\nb\r\nc".splitlines()  # ["a", "b", "c"]

`splitlines` reconoce distintos tipos de saltos de línea y los elimina por defecto. A diferencia de `split("\n")`, gestiona correctamente varios separadores de línea estándar.

Zero-filled string
"42".zfill(5)  # "00042"
"-42".zfill(5)  # "-0042"

`zfill` completa una cadena con ceros a la izquierda hasta un ancho mínimo y coloca los ceros después del signo numérico. No recorta una cadena que ya sea más larga que el ancho solicitado.

Remove exact prefix or suffix
"unhappy".removeprefix("un")  # "happy"
"report.csv".removesuffix(".csv")  # "report"

`removeprefix` y `removesuffix` eliminan una coincidencia exacta solo del extremo correspondiente de la cadena. A diferencia de `strip`, su argumento no se interpreta como un conjunto de caracteres individuales.

Regular expression basics
import re
re.match(r"\d+", text)
re.search(r"\d+", text)
re.findall(r"\d+", text)
re.sub(r"\s+", " ", text)

`match` comprueba solo el inicio de la cadena, `search` encuentra la primera coincidencia en cualquier lugar, `findall` reúne todas las coincidencias y `sub` las sustituye. Un patrón reutilizado suele compilarse una vez con `re.compile`.

Text encoding and decoding
data = "café".encode("utf-8")
text = data.decode("utf-8")

`encode` convierte un `str` en `bytes`, mientras que `decode` realiza la conversión inversa usando una codificación indicada. Usar codificaciones incompatibles puede provocar un error o corromper el texto.

Unicode case folding
"Straße".lower()  # "straße"
"Straße".casefold()  # "strasse"

`lower` realiza una conversión normal a minúsculas, mientras que `casefold` aplica un plegado de mayúsculas más agresivo (ß → ss) para comparar sin distinguir mayúsculas. No es normalización Unicode: una `é` precompuesta y `e` con acento combinante siguen siendo distintas tras `casefold` — para eso está `unicodedata.normalize`.

Listas y slices13

List slicing
a = [0, 1, 2, 3, 4]
a[1:4]    # [1, 2, 3]
a[:3]
a[-2:]
a[::-1]
b = a[:]

Un slice crea una lista nueva. a[:] copia solo el nivel superior — las listas anidadas se comparten.

append / extend / insert / pop
a.append(x)        # O(1)
a.extend([x, y])
a.insert(0, x)     # O(n)
a.pop()            # O(1)
a.pop(0)           # O(n)

Insertar o quitar al principio cuesta O(n). Si necesitas ambos extremos, usa collections.deque.

enumerate
for i, x in enumerate(a):
    ...
for i, x in enumerate(a, start=1):
    ...

Índice y valor a la vez. Sustituye a for i in range(len(a)) y evita el error por uno.

zip
for x, y in zip(a, b):
    ...
list(zip(a, b))
zip(*matrix)

Recorre varias secuencias en paralelo y se detiene en la más corta. zip(*matrix) transpone una matriz.

Comprehensions
[x for x in a if x > 0]
[x * 2 for x in a]
[y for row in m for y in row]
{x: x ** 2 for x in a}

Más corto y rápido que un bucle con append. Dos for aplanan una lista anidada; las llaves con dos puntos crean un diccionario. Los for anidados se leen en el mismo orden que un bucle.

any / all / sum / min / max
any(x > 0 for x in a)
all(x > 0 for x in a)
sum(a)
min(a)
max(a)
max(a, key=abs)
max(a, default=0)

any/all cortan en el primer elemento decisivo. key elige qué comparar; default evita el error con lista vacía.

Building a 2D grid
grid = [[0] * m for _ in range(n)]   # ✓
grid = [[0] * m] * n                # ✗

La segunda forma es incorrecta: multiplicar una lista copia la referencia, no el contenido, y todas las filas serían la misma. Usa comprensión.

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

Un literal o `list()` crea una lista, mientras que la multiplicación repite sus elementos. Al repetir un objeto mutable anidado se copian referencias, por lo que las filas de `rows` quedan vinculadas.

List index and count
a = [4, 2, 4]
i = a.index(4)
n = a.count(4)

`index()` devuelve la posición de la primera coincidencia, mientras que `count()` devuelve el número de coincidencias. `index()` lanza `ValueError` si el valor no existe.

List element removal
a = [10, 20, 30, 20]
a.remove(20)
del a[0]
x = a.pop()

`remove()` elimina el primer valor coincidente, `del` elimina por índice o corte y `pop()` elimina y devuelve un elemento. Un valor ausente en `remove()` o un índice no válido provoca una excepción.

Shallow and deep list copies
from copy import deepcopy

a = [[1], [2]]
b = a.copy()
c = deepcopy(a)
a[0].append(9)

`copy()` crea una nueva lista exterior, pero conserva las referencias a los objetos anidados. `deepcopy()` copia recursivamente el contenido anidado, aunque suele requerir más tiempo y memoria.

List argument unpacking
def add(a, b):
    return a + b

nums = [2, 3]
result = add(*nums)

El operador `*` pasa los elementos de la lista como argumentos posicionales separados. Su cantidad debe coincidir con la firma de la función, salvo que esta acepte `*args`.

Tuple as an immutable sequence
point = (2, 3)
seen = {point}

Una tupla se parece a una lista, pero sus elementos no pueden sustituirse, añadirse ni eliminarse. Puede guardarse en un conjunto o usarse como clave de diccionario solo si todos sus elementos son hashables.

Diccionarios y conjuntos13

Dict access and defaults
d[k]                          # KeyError
d.get(k)                      # None
d.get(k, 0)
d.setdefault(k, []).append(x)

get no lanza excepción. setdefault crea el valor si falta la clave y lo devuelve en un paso.

Iterating a dict
for k in d:
    ...
for k, v in d.items():
    ...
d.keys()
d.values()

Iterar un dict devuelve claves. El orden de inserción se conserva desde Python 3.7.

Counter
from collections import Counter
c = Counter(a)
c[x]                        # 0
c.most_common(2)
Counter(s1) == Counter(s2)

Un contador de frecuencias listo. Una clave ausente da 0, no error; most_common devuelve los más frecuentes.

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

El valor se crea al primer acceso. Útil para agrupar y para listas de adyacencia de grafos.

Sets
s = set(a)
s.add(x)
s.discard(x)
x in s          # O(1)
a_set & b_set
a_set | b_set
a_set - b_set

La pertenencia es O(1) frente a O(n) en listas. & es intersección, | unión, - diferencia. Un set no guarda orden ni duplicados.

The "seen" set pattern
seen = set()
for x in a:
    if x in seen:
        return True
    seen.add(x)

Encuentra un duplicado en una pasada, O(n). La misma forma resuelve two-sum: busca target - x en seen.

Dictionary creation
d1 = {"a": 1}
d2 = dict(a=1)
d3 = dict([("a", 1)])
d4 = {k: 0 for k in ("a", "b")}

Un diccionario puede crearse con un literal, `dict()`, una secuencia de pares o una comprensión de diccionario. Los argumentos nombrados de `dict()` solo sirven para claves de texto que sean identificadores válidos.

Dictionary membership and removal
d = {"a": 1, "c": 3}
"a" in d  # True
x = d.pop("a", 0)
y = d.pop("b", 0)
del d["c"]

En un diccionario, `in` comprueba las claves y no los valores. `del` lanza `KeyError` si la clave no existe, mientras que `pop(key, default)` devuelve el valor alternativo.

Dictionary views
d = {"a": 1, "b": 2}
keys = d.keys()
values = d.values()
pairs = d.items()
d["c"] = 3
list(keys)  # ["a", "b", "c"]

Los métodos `keys()`, `values()` e `items()` devuelven vistas dinámicas, no listas. Reflejan los cambios posteriores del diccionario; para obtener una copia independiente hay que convertirlas explícitamente en `list`.

Dictionary merging
a = {"x": 1}
b = {"x": 2, "y": 3}
c = a | b
a.update(b)

El operador `|` crea un diccionario nuevo, mientras que `update()` modifica el existente. Cuando las claves coinciden, prevalece el valor del diccionario derecho o de la fuente proporcionada.

Dictionary sorting by value
d = {"a": 3, "b": 1, "c": 2}
pairs = sorted(d.items(), key=lambda item: item[1])

Para ordenar por valores, se ordenan los pares de `items()` usando el segundo elemento como clave. El resultado es una lista de tuplas, no un diccionario ordenado automáticamente.

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

Las frecuencias pueden contarse con un diccionario normal usando cero para una clave todavía no vista. Sin un valor por defecto, `get()` devuelve `None`, que no puede sumarse a un número.

Frozenset as a dictionary key
edge = frozenset({2, 5})
weights = {edge: 7}

Un `frozenset` es inmutable y hashable, por lo que puede usarse como clave de diccionario o como elemento de otro conjunto. Un `set` normal no puede utilizarse de esta forma.

Colecciones de la biblioteca estándar10

Deque ends
from collections import deque
q = deque([2, 3])
q.appendleft(1)
q.append(4)
left = q.popleft()
right = q.pop()

deque permite añadir y eliminar elementos por ambos extremos en O(1). Para una cola, usa append y popleft en vez de pop(0) de una lista.

Bounded deque and rotation
from collections import deque
q = deque([1, 2, 3], maxlen=3)
q.append(4)
q.rotate(1)
q.rotate(-1)

maxlen elimina automáticamente elementos del extremo opuesto cuando se llena el deque. rotate desplaza a la derecha con un argumento positivo y a la izquierda con uno negativo.

Min heap
import heapq
h = [4, 1, 7, 2]
heapq.heapify(h)
heapq.heappush(h, 0)
smallest = heapq.heappop(h)

heapq implementa un montículo mínimo, por lo que el menor elemento siempre está en la raíz. heapify funciona en O(n), mientras que push y pop requieren O(log n).

Heap selection and max heap
import heapq
nums = [4, 1, 7, 2]
largest = heapq.nlargest(2, nums)
smallest = heapq.nsmallest(2, nums)
h = [-x for x in nums]
heapq.heapify(h)
maximum = -heapq.heappop(h)

nlargest y nsmallest permiten elegir los k elementos extremos sin ordenar toda la entrada. Un montículo máximo suele simularse guardando los números con signo contrario.

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

Counter almacena frecuencias y admite most_common, update y operaciones entre contadores. Las operaciones aritméticas crean un Counter nuevo y normalmente omiten resultados cero o negativos.

Default dictionary
from collections import defaultdict
groups = defaultdict(list)
counts = defaultdict(int)
seen = defaultdict(set)
groups["a"].append(1)
value = counts["missing"]

defaultdict crea un valor mediante su fábrica al acceder a una clave ausente. Leer con corchetes modifica el diccionario, así que usa in o get si solo quieres comprobarla.

Ordered dictionary
from collections import OrderedDict
d = {"a": 1, "b": 2}
od = OrderedDict(d)
od.move_to_end("a")
first = od.popitem(last=False)

El dict normal ya conserva el orden de inserción, por lo que OrderedDict resulta útil principalmente para reordenar y eliminar desde un extremo concreto. La igualdad entre dos OrderedDict también considera el orden.

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

namedtuple crea un tipo compacto e inmutable con acceso a los campos por nombre e índice. Para modelos más complejos con métodos y valores predeterminados suele ser más cómodo usar dataclass.

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

array almacena valores de un único tipo primitivo de forma más compacta que una lista, aunque rara vez se necesita en problemas algorítmicos. bytes es inmutable y bytearray permite modificar bytes individuales.

Chain map
from collections import ChainMap
defaults = {"timeout": 30}
overrides = {"timeout": 10}
config = ChainMap(overrides, defaults)
value = config["timeout"]

ChainMap busca en varios diccionarios de izquierda a derecha sin copiarlos ni combinarlos. Las asignaciones modifican únicamente el primer diccionario de forma predeterminada.

Iteración y generadores10

Generator expression and list comprehension
squares_list = [x * x for x in values]
squares_gen = (x * x for x in values)

Una comprensión de lista crea inmediatamente toda la lista, mientras que una expresión generadora produce valores bajo demanda. El generador ahorra memoria, pero normalmente solo puede consumirse una vez.

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

La presencia de yield convierte la función en generadora: la llamada devuelve un generador y el cuerpo se ejecuta al iterarlo. Después de cada yield, la ejecución continúa desde la posición guardada.

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

yield from transmite los elementos de un iterable anidado sin escribir manualmente un bucle interior. Cada uso aplana solo un nivel.

Iter, next, and StopIteration
it = iter([10])
first = next(it)
missing = next(it, None)

try:
    next(it)
except StopIteration:
    ...

iter obtiene un iterador y next extrae el siguiente elemento avanzando su estado. Proporcionar un valor predeterminado evita StopIteration cuando el iterador se agota.

Count, cycle, and islice
from itertools import count, cycle, islice

numbers = list(islice(count(10, 2), 4))
pattern = list(islice(cycle("ab"), 5))

count y cycle crean iteradores potencialmente infinitos, mientras que islice los limita sin materializar toda la secuencia. islice no admite índices negativos ni un paso negativo.

Chain
from itertools import chain

merged = list(chain([1, 2], [3, 4]))
flat = list(chain.from_iterable([[1, 2], [3, 4]]))

chain recorre de forma perezosa varios iterables uno tras otro. chain.from_iterable recibe un iterable exterior y aplana un nivel.

Product, permutations, and combinations
from itertools import combinations, permutations, product

pairs = list(product([0, 1], repeat=2))
orders = list(permutations([1, 2, 3], 2))
choices = list(combinations([1, 2, 3], 2))

product construye un producto cartesiano, permutations tiene en cuenta el orden y combinations no. Devuelven iteradores, pero el número de resultados puede crecer de forma combinatoria.

Groupby
from itertools import groupby

items = [1, 1, 2, 2, 2, 3]
groups = [(key, list(group)) for key, group in groupby(items)]

groupby agrupa únicamente elementos consecutivos con la misma clave. Para una agrupación global, normalmente hay que ordenar antes por esa misma clave.

Reversed
a = [1, 2, 3]
it = reversed(a)
b = list(it)

reversed devuelve un iterador inverso y no modifica la secuencia original. El objeto debe admitir iteración inversa o proporcionar acceso por índice y longitud.

Generator aggregation
total = sum(x * x for x in values)
has_negative = any(x < 0 for x in values)
all_even = all(x % 2 == 0 for x in values)

sum consume todos los valores del generador, mientras que any y all se detienen en cuanto se conoce el resultado. Con una fuente vacía, any devuelve False y all devuelve True.

Números y matemáticas10

Arbitrary-precision integers
n = 10**100
n.bit_length()

El tipo `int` almacena enteros de tamaño arbitrario, por lo que no existe desbordamiento de ancho fijo. Los límites prácticos son la memoria y el tiempo de cálculo, pero desde Python 3.11 la conversión entre `int` y `str` está limitada a 4300 dígitos y lanza `ValueError` al superarlos.

Floating-point precision
0.1 + 0.2 == 0.3  # False
x = float("inf")

El tipo `float` usa aproximaciones binarias, por lo que muchas fracciones decimales no pueden representarse exactamente. Usa `math.isclose` para comparar y `decimal.Decimal` cuando necesites aritmética decimal exacta.

Bankers rounding
round(2.5)  # 2
round(3.5)  # 4
round(12.345, 2)

`round` resuelve un empate exacto hacia el valor par más cercano en vez de redondear siempre hacia arriba. El resultado con valores `float` también puede verse afectado por el error de representación binaria.

Absolute value and powers
abs(-7)  # 7
2**10  # 1024
pow(2, 10)  # 1024
pow(2, 10, 1000)  # 24

`abs` devuelve el valor absoluto, mientras que `**` y `pow` realizan la potenciación. La forma `pow(a, b, mod)` calcula eficientemente una potencia modular sin crear un entero intermedio enorme.

Math roots and number theory
from math import sqrt, isqrt, gcd, factorial
sqrt(81)  # 9.0
isqrt(80)  # 8
gcd(18, 24)  # 6
factorial(5)  # 120

`sqrt` devuelve una raíz de punto flotante, mientras que `isqrt` devuelve la parte entera exacta sin errores de `float`. `gcd` calcula el máximo común divisor y `factorial` solo acepta enteros no negativos.

Floor, ceiling and infinity
from math import ceil, floor, inf
ceil(2.1)  # 3
floor(2.9)  # 2
inf > 10**100  # True

`ceil` redondea hacia infinito positivo y `floor` hacia infinito negativo, algo especialmente importante con números negativos. `math.inf` se compara como infinito, pero no aparece automáticamente al dividir entre cero.

Division by zero
try:
    1 / 0
except ZeroDivisionError:
    ...

La división, la división entera y el módulo con divisor cero lanzan `ZeroDivisionError`. Python no convierte esa división en infinito.

Numeric conversions and bases
int("101", 2)  # 5
int("0xff", 0)  # 255
int(3.9)  # 3
float("2.5")  # 2.5
str(42)  # "42"
bin(10)  # "0b1010"
hex(255)  # "0xff"
oct(8)  # "0o10"

`int` puede interpretar cadenas con bases de 2 a 36, mientras que la base `0` detecta automáticamente un prefijo estándar. Convertir un `float` a `int` trunca hacia cero, y `bin`, `hex` y `oct` devuelven cadenas con prefijo.

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

Las operaciones bit a bit trabajan con la representación binaria de los enteros y suelen usarse para máscaras y conjuntos de estados. Los enteros no tienen un ancho fijo y `~x` siempre equivale a `-x - 1`.

Pseudo-random values
import random
rng = random.Random(42)
rng.randrange(1, 10)
rng.randint(1, 10)
rng.choice([1, 2, 3])
a = [1, 2, 3]
rng.shuffle(a)

`random` genera valores pseudoaleatorios reproducibles: `randrange` excluye el límite final, mientras que `randint` incluye ambos extremos. El módulo no es adecuado para contraseñas ni criptografía.

Ordenación y búsqueda6

sorted / list.sort
b = sorted(a)
a.sort()
sorted(a, reverse=True)

sorted devuelve una lista nueva; sort ordena en el sitio y devuelve None. La ordenación es estable.

Sort key
sorted(words, key=len)
sorted(pairs, key=lambda p: p[1])
sorted(a, key=lambda x: (-x[1], x[0]))

key se llama una vez por elemento. Una tupla da varios niveles; el menos invierte un campo numérico.

Stable sort
a.sort(key=lambda x: x.age)
a.sort(key=lambda x: x.name)

Los elementos iguales conservan su orden, así que puedes ordenar primero por la clave menos significativa.

itemgetter
from operator import itemgetter
sorted(rows, key=itemgetter(1, 0))

Igual que una lambda pero más rápido, y más claro al ordenar por varios campos.

bisect
from bisect import bisect_left, bisect_right, insort
i = bisect_left(a, x)
j = bisect_right(a, x)
insort(a, x)

Búsqueda binaria en una lista ordenada en O(log n). bisect_left da la primera posición de x y bisect_right la siguiente, así que j - i es el número de apariciones. insort inserta manteniendo el orden.

Binary search by hand
lo, hi = 0, len(a) - 1
while lo <= hi:
    mid = (lo + hi) // 2
    if a[mid] == x:
        return mid
    if a[mid] < x:
        lo = mid + 1
    else:
        hi = mid - 1
return -1

La lista debe estar ordenada. Mueve los límites más allá de mid o el bucle no termina.

Pila, cola y dos punteros9

List as stack
stack = []
stack.append(1)
stack.append(2)
top = stack.pop()

Una lista implementa eficazmente una pila LIFO con append y pop desde el final en O(1) amortizado. Eliminar desde el inicio de una lista requiere O(n).

Deque as queue
from collections import deque
q = deque([1, 2])
q.append(3)
first = q.popleft()

Una cola FIFO se implementa eficazmente con deque: append añade a la derecha y popleft elimina a la izquierda en O(1). Una lista con pop(0) no es adecuada para colas grandes porque desplaza los elementos.

Monotonic stack
def next_greater(nums):
    result = [-1] * len(nums)
    stack = []
    for i, x in enumerate(nums):
        while stack and nums[stack[-1]] < x:
            result[stack.pop()] = x
        stack.append(i)
    return result

Una pila monótona mantiene valores o índices ordenados dentro de la pila y encuentra el elemento mayor o menor más cercano en O(n). Conviene guardar índices cuando se necesitan distancias o posiciones.

Balanced brackets
def is_balanced(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in s:
        if ch in "([{":
            stack.append(ch)
        elif ch in pairs and (not stack or stack.pop() != pairs[ch]):
            return False
    return not stack

Los símbolos de apertura se guardan en una pila y cada cierre debe coincidir con la cima. Al terminar, la pila debe estar vacía o quedarán símbolos sin cerrar.

Opposite pointers
def has_pair(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        total = nums[left] + nums[right]
        if total == target:
            return True
        if total < target:
            left += 1
        else:
            right -= 1
    return False

Los punteros desde extremos opuestos reducen el espacio de búsqueda cuando el resultado actual indica cuál debe moverse. Para buscar una pareja con una suma dada, el array debe estar ordenado.

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

Un puntero rápido avanza más que uno lento, lo que permite encontrar el centro o detectar ciclos sin memoria adicional. Antes del salto doble hay que comprobar fast y su nodo siguiente.

Remove duplicates in place
def remove_duplicates(nums):
    if not nums:
        return 0
    write = 1
    for read in range(1, len(nums)):
        if nums[read] != nums[write - 1]:
            nums[write] = nums[read]
            write += 1
    return write

Un puntero de lectura recorre el array y uno de escritura construye el prefijo válido sin otro array. Esta versión supone que los valores iguales están juntos, normalmente porque la entrada está ordenada.

Reverse in place
def reverse_in_place(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

Intercambiar elementos desde ambos extremos invierte una secuencia mutable en O(n) y con O(1) de memoria adicional. Una cadena no puede modificarse así porque es inmutable.

Merge sorted sequences
def merge_sorted(a, b):
    i = j = 0
    result = []
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            result.append(a[i])
            i += 1
        else:
            result.append(b[j])
            j += 1
    result.extend(a[i:])
    result.extend(b[j:])
    return result

Dos punteros fusionan secuencias ordenadas en O(n + m), eligiendo cada vez el menor elemento actual. Después del bucle principal hay que añadir la parte restante.

Ventana deslizante y sumas de prefijos9

Fixed sliding window
def window_sums(nums, k):
    if k > len(nums):
        return []
    current = sum(nums[:k])
    result = [current]
    for right in range(k, len(nums)):
        current += nums[right] - nums[right - k]
        result.append(current)
    return result

Una ventana fija actualiza su estado añadiendo el elemento que entra y eliminando el que sale, por lo que procesa todas las ventanas en O(n). Hay que definir el comportamiento cuando k es cero o supera la longitud.

Variable sliding window
def min_length(nums, target):
    left = 0
    total = 0
    answer = len(nums) + 1
    for right, x in enumerate(nums):
        total += x
        while total >= target:
            answer = min(answer, right - left + 1)
            total -= nums[left]
            left += 1
    return 0 if answer > len(nums) else answer

Una ventana variable se amplía por la derecha y se contrae por la izquierda mientras se cumple la condición. Las versiones basadas en sumas suelen requerir valores no negativos para conservar la monotonicidad.

Window frequency counter
from collections import Counter

def anagram_windows(s, pattern):
    need = Counter(pattern)
    window = Counter()
    result = []
    k = len(pattern)
    for right, ch in enumerate(s):
        window[ch] += 1
        if right >= k:
            left_ch = s[right - k]
            window[left_ch] -= 1
            if window[left_ch] == 0:
                del window[left_ch]
        if window == need:
            result.append(right - k + 1)
    return result

Las frecuencias de la ventana se actualizan solo para el elemento que entra y el que sale, evitando recálculos. Conviene eliminar los conteos cero para que las comparaciones sean más claras.

Maximum window sum
def max_window_sum(nums, k):
    current = sum(nums[:k])
    best = current
    for right in range(k, len(nums)):
        current += nums[right] - nums[right - k]
        best = max(best, current)
    return best

La suma de la siguiente ventana se obtiene restando el elemento izquierdo y añadiendo el nuevo elemento derecho. Si puede haber valores negativos, inicializa el máximo con la primera ventana completa y no con cero.

Prefix sums
def range_sum(prefix, left, right):
    return prefix[right] - prefix[left]

nums = [2, 4, 1]
prefix = [0]
for x in nums:
    prefix.append(prefix[-1] + x)
value = range_sum(prefix, 1, 3)

Un array de prefijos con un cero inicial permite obtener la suma del intervalo semiabierto [left, right) en O(1). El cero adicional simplifica los límites y los rangos que comienzan en el primer elemento.

Adjacent differences
nums = [3, 8, 10, 15]
diffs = [b - a for a, b in zip(nums, nums[1:])]

Las diferencias adyacentes describen los cambios entre posiciones y permiten reconstruir valores mediante acumulación. El resultado contiene un elemento menos que la secuencia original.

Running accumulation
from itertools import accumulate
prefix = list(accumulate([2, 4, 1], initial=0))
products = list(accumulate([2, 3, 4], lambda a, b: a * b))

accumulate produce de forma perezosa los resultados intermedios y acepta una función binaria personalizada. initial añade un valor inicial a la salida y cambia su longitud.

Target subarray sum
def has_subarray_sum(nums, target):
    seen = {0}
    prefix = 0
    for x in nums:
        prefix += x
        if prefix - target in seen:
            return True
        seen.add(prefix)
    return False

Un subarray suma target cuando dos sumas de prefijo difieren en target. Un conjunto de prefijos permite O(n) y funciona con valores negativos, a diferencia de una ventana deslizante basada en suma.

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

La búsqueda binaria sobre la respuesta encuentra el límite donde un predicado monótono cambia de False a True. La dificultad principal es elegir límites inclusivos coherentes y demostrar la monotonicidad de feasible.

Recursión y programación dinámica10

Recursive base and step
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

El caso base detiene la recursión y el paso recursivo reduce el problema a una instancia menor. Sin un caso base alcanzable, las llamadas terminan con RecursionError.

Recursion depth limit
import sys
sys.setrecursionlimit(200000)

Python limita la profundidad de la pila y lanza RecursionError cuando se supera. El límite debe aumentarse con cuidado porque una recursión muy profunda puede agotar la pila del sistema.

Tail recursion
def factorial_tail(n, acc=1):
    if n <= 1:
        return acc
    return factorial_tail(n - 1, acc * n)

Una llamada de cola es la última operación de una función, pero Python no optimiza estas llamadas. La recursión de cola sigue consumiendo pila y normalmente se sustituye por un bucle.

Memoization decorators
from functools import cache, lru_cache

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

@cache
def fib_cache(n):
    return n if n < 2 else fib_cache(n - 1) + fib_cache(n - 2)

Estos decoradores guardan los resultados según los argumentos y eliminan cálculos repetidos. cache equivale a un lru_cache sin límite y todos los argumentos deben ser hashables.

Bottom-up dynamic programming
def fib(n):
    if n < 2:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

La DP ascendente calcula los estados desde los más simples hasta los más complejos y los guarda en un array. Evita la pila de recursión, aunque a veces el array puede reducirse a unas pocas variables.

Two-dimensional dynamic programming
def grid_paths(rows, cols):
    dp = [[0] * cols for _ in range(rows)]
    for r in range(rows):
        dp[r][0] = 1
    for c in range(cols):
        dp[0][c] = 1
    for r in range(1, rows):
        for c in range(1, cols):
            dp[r][c] = dp[r - 1][c] + dp[r][c - 1]
    return dp[-1][-1]

Se usa una tabla bidimensional cuando un estado depende de dos parámetros, como una posición de fila y columna. Es importante inicializar correctamente los bordes antes de rellenar las demás celdas.

DP answer reconstruction
def min_coins(coins, amount):
    inf = amount + 1
    dp = [0] + [inf] * amount
    prev = [-1] * (amount + 1)
    for total in range(1, amount + 1):
        for coin in coins:
            if coin <= total and dp[total - coin] + 1 < dp[total]:
                dp[total] = dp[total - coin] + 1
                prev[total] = coin
    if dp[amount] == inf:
        return None
    answer = []
    while amount > 0:
        coin = prev[amount]
        answer.append(coin)
        amount -= coin
    return answer

Además del valor óptimo, la DP puede guardar la elección usada para alcanzar cada estado. Después, la respuesta se reconstruye recorriendo hacia atrás desde el estado final.

Divide and conquer
def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result + left[i:] + right[j:]

El problema se divide en subproblemas independientes cuyas soluciones recursivas se combinan después. La eficiencia depende de que la división esté equilibrada y del coste de combinar los resultados.

Backtracking permutations
def permutations(a):
    result = []

    def backtrack(start):
        if start == len(a):
            result.append(a.copy())
            return
        for i in range(start, len(a)):
            a[start], a[i] = a[i], a[start]
            backtrack(start + 1)
            a[start], a[i] = a[i], a[start]

    backtrack(0)
    return result

El backtracking elige una opción, continúa recursivamente y después restaura el estado modificado. Generar todas las permutaciones requiere tiempo factorial.

Backtracking subsets
def subsets(a):
    result = []
    current = []

    def backtrack(index):
        if index == len(a):
            result.append(current.copy())
            return
        backtrack(index + 1)
        current.append(a[index])
        backtrack(index + 1)
        current.pop()

    backtrack(0)
    return result

Para cada elemento, la recursión considera dos opciones: incluirlo u omitirlo. El resultado actual debe copiarse al guardarlo y restaurarse después de la llamada recursiva.

Grafos y árboles10

Adjacency list
edges = [(0, 1), (0, 2), (1, 2)]
graph = {0: [], 1: [], 2: []}
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)

Una lista de adyacencia guarda los vecinos de cada vértice y normalmente requiere O(V + E) de memoria. En un grafo dirigido no se debe añadir automáticamente la arista inversa.

Shortest path in an unweighted graph
from collections import deque

def shortest_path(graph, start, target):
    queue = deque([start])
    parent = {start: None}
    while queue:
        node = queue.popleft()
        if node == target:
            break
        for neighbor in graph[node]:
            if neighbor not in parent:
                parent[neighbor] = node
                queue.append(neighbor)
    if target not in parent:
        return None
    path = []
    node = target
    while node is not None:
        path.append(node)
        node = parent[node]
    return path[::-1]

BFS alcanza por primera vez un vértice mediante un camino con el número mínimo de aristas. Para reconstruir el camino basta con guardar el padre de cada vértice visitado por primera vez.

Tree depth-first traversals
def preorder(node, result):
    if node is None:
        return
    result.append(node.val)
    preorder(node.left, result)
    preorder(node.right, result)

def inorder(node, result):
    if node is None:
        return
    inorder(node.left, result)
    result.append(node.val)
    inorder(node.right, result)

def postorder(node, result):
    if node is None:
        return
    postorder(node.left, result)
    postorder(node.right, result)
    result.append(node.val)

Preorder procesa el nodo antes que sus hijos, inorder entre los subárboles izquierdo y derecho, y postorder después de los hijos. Inorder produce un orden ordenado solo en un árbol binario de búsqueda válido.

Tree level-order traversal
from collections import deque

def level_order(root):
    if root is None:
        return []
    queue = deque([root])
    levels = []
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left is not None:
                queue.append(node.left)
            if node.right is not None:
                queue.append(node.right)
        levels.append(level)
    return levels

El recorrido por niveles es un BFS aplicado a un árbol y utiliza una cola. El tamaño de la cola al inicio de una iteración fija el número de nodos del nivel actual.

Directed cycle detection
def has_cycle(graph):
    state = {}

    def dfs(node):
        state[node] = 1
        for neighbor in graph[node]:
            if state.get(neighbor, 0) == 1:
                return True
            if state.get(neighbor, 0) == 0 and dfs(neighbor):
                return True
        state[node] = 2
        return False

    return any(state.get(node, 0) == 0 and dfs(node) for node in graph)

En un grafo dirigido, una arista hacia un vértice que está en la pila DFS indica un ciclo. Se suelen usar tres estados: no visitado, en procesamiento y completamente procesado.

Topological sort
from collections import deque

def topological_sort(graph):
    indegree = {node: 0 for node in graph}
    for node in graph:
        for neighbor in graph[node]:
            indegree[neighbor] += 1
    queue = deque(node for node in graph if indegree[node] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph[node]:
            indegree[neighbor] -= 1
            if indegree[neighbor] == 0:
                queue.append(neighbor)
    return order if len(order) == len(graph) else None

El algoritmo de Kahn elimina repetidamente los vértices con grado de entrada cero. Si no se procesan todos los vértices, el grafo contiene un ciclo y no existe un orden topológico.

Disjoint set union
class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n

    def find(self, x):
        while x != self.parent[x]:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def union(self, a, b):
        a = self.find(a)
        b = self.find(b)
        if a == b:
            return False
        if self.size[a] < self.size[b]:
            a, b = b, a
        self.parent[b] = a
        self.size[a] += self.size[b]
        return True

Union-Find comprueba rápidamente si dos vértices pertenecen a la misma componente y une componentes. La compresión de caminos y la unión por tamaño ofrecen un tiempo amortizado casi constante.

Excepciones10

Try, except, else, finally
try:
    value = int(text)
except ValueError:
    value = 0
else:
    value += 1
finally:
    closed = True

except se ejecuta tras una excepción, else solo si try termina correctamente y finally se ejecuta siempre. Conviene incluir en try únicamente las operaciones cuyas excepciones se esperan.

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

Captura un tipo de excepción concreto para no ocultar errores ajenos. Un except sin tipo solo es apropiado en casos poco frecuentes donde la excepción se gestiona correctamente o se vuelve a lanzar.

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

Varios tipos de excepción con el mismo tratamiento pueden agruparse en una tupla dentro de un solo except. Si el tratamiento es distinto, deben usarse bloques except separados.

Raise and re-raise
def load_number(text):
    try:
        return int(text)
    except ValueError as exc:
        raise ValueError("invalid number") from exc

def parse_number(text):
    try:
        return load_number(text)
    except ValueError:
        raise

raise crea una excepción, mientras que raise sin argumentos vuelve a lanzar la actual. La forma raise ... from ... conserva una cadena causal explícita entre excepciones.

Custom exception
class InvalidStateError(Exception):
    pass

def require_ready(ready):
    if not ready:
        raise InvalidStateError("not ready")

Las excepciones propias heredan de Exception y permiten distinguir errores del dominio de fallos integrados. A menudo basta con una clase vacía con un nombre claro.

Common built-in exceptions
def built_in_errors(data, key, index, value, divisor):
    if not isinstance(value, int):
        raise TypeError
    if divisor == 0:
        raise ZeroDivisionError
    if key not in data:
        raise KeyError(key)
    if not -len(data) <= index < len(data):
        raise IndexError(index)
    if value < 0:
        raise ValueError(value)

ValueError indica un valor no válido de un tipo aceptable y TypeError un tipo no válido; KeyError e IndexError representan claves o índices inexistentes. ZeroDivisionError aparece al dividir o calcular el resto entre cero.

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

assert comprueba invariantes internos y lanza AssertionError cuando no se cumplen. Las comprobaciones se eliminan al ejecutar Python con -O, por lo que no deben validar datos del usuario.

With context manager
with open("data.txt", encoding="utf-8") as file:
    text = file.read()

Un gestor de contexto garantiza la liberación del recurso tanto al finalizar normalmente como al producirse una excepción. En los archivos, esto implica el cierre automático al salir del bloque with.

Suppress exceptions
from contextlib import suppress
from pathlib import Path

path = Path("data.txt")
with suppress(FileNotFoundError):
    path.unlink()

contextlib.suppress ignora únicamente los tipos de excepción indicados y resulta útil para casos breves y esperados. No debe usarse para ocultar errores que requieren tratamiento o registro.

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

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

EAFP realiza primero la operación y gestiona el fallo esperado, mientras que LBYL comprueba antes una condición. EAFP suele ser más idiomático en Python y evita diferencias entre la comprobación y la operación.

Clases y objetos10

Class, initializer, and instance method
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

Una clase describe la estructura de los objetos y __init__ inicializa cada instancia nueva. self se pasa automáticamente y representa el objeto actual.

Instance and class attributes
class Counter:
    total = 0

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

Un atributo de clase se comparte entre todas las instancias, mientras que un atributo de self pertenece a un objeto. Modificar un atributo de clase mutable puede afectar inesperadamente a todas las instancias.

Property
class Account:
    def __init__(self, balance):
        self.balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError
        self._balance = value

@property permite usar la lógica de un método mediante el acceso normal a un atributo y validar lecturas o escrituras. Normalmente guarda el dato con otro nombre para evitar una recursión infinita.

Static and class methods
class Temperature:
    def __init__(self, value):
        self.value = value

    @staticmethod
    def is_valid(value):
        return value >= -273.15

    @classmethod
    def zero(cls):
        return cls(0)

@staticmethod no recibe ni una instancia ni la clase, mientras que @classmethod recibe la clase mediante cls. Los métodos de clase sirven como constructores alternativos y respetan las subclases.

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

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

Una subclase hereda el comportamiento de su clase base y puede ampliarlo o sobrescribirlo. super() llama a la siguiente implementación según el orden de resolución de métodos, algo especialmente importante con herencia múltiple.

String representations
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point(x={self.x}, y={self.y})'

    def __str__(self):
        return f'{self.x}, {self.y}'

__repr__ está pensado para desarrolladores y depuración, mientras que __str__ ofrece una salida legible para el usuario. Si no existe __str__, str() utiliza __repr__.

Equality and hashing
class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __eq__(self, other):
        return isinstance(other, User) and self.user_id == other.user_id

    def __hash__(self):
        return hash(self.user_id)

__eq__ define la igualdad lógica y __hash__ permite usar el objeto en conjuntos o como clave de diccionario. Los objetos iguales deben tener el mismo hash y los campos utilizados para calcularlo no deben cambiar.

Object ordering with __lt__
class Item:
    def __init__(self, score, name):
        self.score = score
        self.name = name

    def __lt__(self, other):
        return (self.score, self.name) < (other.score, other.name)

items = sorted([Item(2, 'b'), Item(1, 'a')])

__lt__ define el operador < y permite que sorted() compare objetos directamente. La comparación de tuplas permite expresar cómodamente el criterio principal y los desempates.

Frozen dataclass with defaults
from dataclasses import dataclass, field

@dataclass(frozen=True)
class Node:
    value: int
    tags: tuple[str, ...] = ()

@dataclass
class Basket:
    items: list[str] = field(default_factory=list)

@dataclass genera métodos como __init__ y __repr__, mientras que frozen=True impide la asignación normal a los campos. Un valor por defecto inmutable se escribe directamente, pero uno mutable como [] lanza ValueError al crear la clase y exige default_factory.

Slots
class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

__slots__ limita los atributos disponibles de una instancia y suele reducir el uso de memoria. Si no se incluye __dict__, no se pueden añadir dinámicamente otros atributos.

Anotaciones de tipos10

Variable and function annotations
count: int = 3

def repeat(text: str, times: int) -> str:
    return text * times

count = '3'

Las anotaciones describen los tipos esperados para analizadores estáticos, IDE y documentación. Python no las comprueba automáticamente durante la ejecución y permite valores de otro tipo.

Collection annotations
items: list[int] = [1, 2]
scores: dict[str, int] = {'a': 1}
point: tuple[int, int] = (2, 3)

Las colecciones integradas pueden parametrizarse con los tipos de sus elementos, claves y valores. tuple[int, int] describe una estructura fija, mientras que tuple[int, ...] describe una tupla de longitud variable.

Optional values
from typing import Optional

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

Optional[int] e int | None indican que el valor puede ser int o None. Optional no convierte un parámetro en opcional; para ello necesita un valor por defecto.

Union types
from typing import Union

value_a: Union[int, str] = 1
value_b: int | str = 'x'

Union[int, str] e int | str permiten uno de varios tipos. Las uniones demasiado amplias dificultan el estrechamiento de tipos y el tratamiento de todos los casos.

Any
from typing import Any

value: Any = 1
value = 'x'
value = [1, 2]

Any desactiva las comprobaciones estáticas de las operaciones sobre un valor y es compatible con cualquier tipo en ambos sentidos. Su uso excesivo oculta errores y elimina gran parte de la utilidad de las anotaciones.

Callable
from collections.abc import Callable

def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
    return fn(a, b)

Callable[[A, B], R] describe un objeto invocable que recibe dos argumentos y devuelve R. Callable[..., R] se usa cuando no se conoce la lista exacta de parámetros.

Iterable and Sequence
from collections.abc import Iterable, Sequence

def total(values: Iterable[int]) -> int:
    return sum(values)

def first(values: Sequence[int]) -> int:
    return values[0]

Iterable garantiza que el objeto puede recorrerse, pero no que admita índices ni varios recorridos. Sequence también proporciona longitud y acceso por índice, por lo que es un contrato más estricto.

Type aliases and Literal
from typing import Literal, TypeAlias

UserId: TypeAlias = int
Mode: TypeAlias = Literal['fast', 'safe']

TypeAlias asigna un nombre legible a otro tipo, pero no crea un tipo nuevo e incompatible. Literal limita el valor a literales concretos y resulta útil para modos y estados.

TypedDict
from typing import TypedDict

class UserData(TypedDict):
    name: str
    age: int

user: UserData = {'name': 'a', 'age': 20}

TypedDict describe las claves obligatorias y los tipos de valores de un diccionario normal. Durante la ejecución el objeto sigue siendo un dict y la estructura solo la comprueban herramientas estáticas.

Generic and TypeVar
from typing import Generic, TypeVar

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, value: T):
        self.value = value

    def get(self) -> T:
        return self.value

TypeVar relaciona tipos en distintas partes de una firma y Generic crea una clase parametrizada por ese tipo. A diferencia de Any, el analizador conserva el tipo concreto en cada uso.

Pruebas y depuración10

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

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

pytest detecta funciones cuyo nombre empieza por test (python_functions = test* por defecto; test_ por convención) y considera un assert fallido como un fallo del test. Conviene comparar resultados concretos para obtener diferencias útiles en el informe.

Expected exception with pytest.raises
import pytest

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

pytest.raises comprueba que un bloque lance el tipo de excepción esperado. El test falla si no se produce ninguna excepción o si el tipo es distinto.

Parameterized test
import pytest

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

parametrize ejecuta una misma función de test para cada conjunto de datos. Los casos siguen siendo independientes, por lo que el informe identifica la entrada concreta que ha fallado.

Pytest fixture
import pytest

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

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

Una fixture prepara datos o recursos y se inyecta en el test mediante un parámetro con el mismo nombre. Las fixtures demasiado amplias crean dependencias ocultas y dificultan la comprensión del test.

Approximate numeric comparison
import pytest

def test_ratio():
    assert 0.1 + 0.2 == pytest.approx(0.3)

pytest.approx compara números usando una tolerancia absoluta o relativa. La igualdad directa entre float no es fiable porque muchas fracciones decimales no tienen una representación binaria exacta.

Unittest TestCase
import unittest

class MathTest(unittest.TestCase):
    def test_add(self):
        self.assertEqual(2 + 3, 5)

unittest.TestCase es el modelo de pruebas basado en clases de la biblioteca estándar e incluye métodos como assertEqual, setUp y tearDown. Los métodos de prueba deben empezar por el prefijo test (TestLoader.testMethodPrefix) para que el cargador predeterminado los detecte; por convención se escribe test_.

Doctest
def square(x):
    """
    >>> square(3)
    9
    """
    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

doctest ejecuta ejemplos incluidos en docstrings y compara su salida con el resultado escrito. Es útil para ejemplos pequeños, pero resulta frágil cuando cambia el formato de la salida.

Benchmarking with timeit
from timeit import timeit

elapsed = timeit('sum(range(100))', number=10_000)

timeit ejecuta repetidamente un fragmento pequeño y reduce el efecto del ruido aleatorio de la medición. Deben compararse operaciones equivalentes en las mismas condiciones y no basarse en una sola ejecución.

Printing a traceback
import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()

traceback.print_exc() muestra la traza activa desde un manejador de excepciones. Capturar un error sin registrarlo ni volver a lanzarlo es peligroso porque oculta la causa original.

Entrada, salida y archivos10

Read one input line
name = input().strip()

`input` lee una línea de la entrada estándar, elimina el salto de línea final y siempre devuelve un `str`. Los espacios de los extremos permanecen salvo que se llame a `strip`.

Read all standard input
import sys
tokens = sys.stdin.buffer.read().split()

`sys.stdin.buffer.read` lee rápidamente toda la entrada como `bytes`, y `split` la divide por cualquier espacio en blanco. Este enfoque es cómodo para problemas algorítmicos, pero conserva toda la entrada en memoria.

Read standard input line by line
import sys
while line := sys.stdin.readline():
    line = line.rstrip("\r\n")

`sys.stdin.readline` conserva el salto de línea final y devuelve una cadena vacía al llegar al final de la entrada. La lectura línea por línea evita cargar todos los datos en memoria.

Configure printed output
import sys
print(a, b, sep=",", end="\n", file=sys.stderr)

`sep` define el separador entre argumentos, `end` define el final de la salida y `file` selecciona el flujo de texto de destino. Muchas llamadas individuales a `print` pueden ser más lentas que construir una sola cadena con `join`.

Read and write text files
with open("input.txt", "r", encoding="utf-8") as f:
    text = f.read()
with open("output.txt", "w", encoding="utf-8") as f:
    f.write(text)

`with open` garantiza que el archivo se cierre incluso si ocurre una excepción, y una codificación explícita hace que el comportamiento sea portable. El modo `w` vacía el archivo existente antes de escribir.

JSON serialization
import json
data = json.loads(text)
text = json.dumps(data, ensure_ascii=False)

`json.loads` convierte una cadena JSON en objetos de Python, mientras que `json.dumps` realiza la conversión inversa. JSON no conserva la diferencia entre listas y tuplas, y las claves de los objetos son cadenas después de analizarlas.

CSV reading and writing
import csv
with open("data.csv", newline="", encoding="utf-8") as f:
    rows = list(csv.reader(f))
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

`csv.reader` y `csv.writer` gestionan correctamente los delimitadores, las comillas y los saltos de línea dentro de los campos. Los archivos CSV deben abrirse con `newline=""`, y los valores leídos permanecen como cadenas por defecto.

Path objects
from pathlib import Path
path = Path("data") / "input.txt"
text = path.read_text(encoding="utf-8")
path.write_text(text, encoding="utf-8")

`pathlib.Path` crea objetos de ruta portables y une sus partes con `/`. `read_text` y `write_text` son cómodos para archivos pequeños, pero procesan todo el contenido de una vez.

Environment variables
import os
token = os.environ.get("API_TOKEN")

`os.environ` expone las variables de entorno del proceso como un mapeo mutable de cadenas. El acceso con corchetes lanza `KeyError` si falta la clave, mientras que `get` devuelve `None` o un valor predeterminado.

Command-line arguments
import sys
script, *args = sys.argv

`sys.argv` contiene los argumentos de la línea de comandos como cadenas, y el elemento de índice `0` suele ser el nombre del script ejecutado. No realiza validación ni conversión; para una interfaz estructurada se usa `argparse`.