Yeda AI Tips · #122

English

sum() transmite en flujo perezoso, sum([]) construye primero

Dos corchetes están consumiendo tu RAM en Python sin que lo notes. sum([x * x for x in data]) y sum(x * x for x in data) devuelven exactamente el mismo número — pero el primero materializa una lista completa en memoria antes de sumar un solo valor, y el segundo procesa los elementos uno a la vez. Con un millón de elementos, esa es la diferencia entre retener un millón de valores intermedios a la vez y retener aproximadamente uno.

Qué hacen realmente los corchetes

Una list comprehension[x * x for x in data] — construye un objeto list completo. Cada elemento se calcula, se empaqueta y se almacena antes de que sum() vea alguno. Esa lista existe solo para consumirse una vez y descartarse.

Una expresión generadora(x * x for x in data) — devuelve un iterador. Según la referencia del lenguaje Python, sus valores se "evalúan de forma perezosa … cuando se le pide al iterador que produzca un valor". sum() extrae un valor, lo suma al total acumulado, y el valor se convierte en basura de inmediato. La lista completa nunca existe.

La sintaxis de Python hace que el arreglo sea casi de un carácter: el PEP 289 (el PEP de las expresiones generadoras, definitivo desde Python 2.4) especifica que "si una llamada a función tiene un único argumento posicional, puede ser una expresión generadora sin paréntesis adicionales". Así que dentro de sum(...), simplemente borra los corchetes:

# Builds a 1,000,000-element list first, then folds it
total = sum([x * x for x in range(1_000_000)])

# Streams one value at a time — same total, near-zero extra memory
total = sum(x * x for x in range(1_000_000))

Cuándo gana cada forma

SituaciónUsaPor qué
Reducir a un solo valor (sum, max, min, any, all)expresión generadorael consumidor extrae valores de forma perezosa; no hace falta lista
Necesitas los valores otra vez (indexar, len, dos pasadas)list comprehensionun generador es de un solo uso — una vez consumido, queda vacío
Entradas muy grandes o flujos sin límiteexpresión generadoralas list comprehensions "no sirven … con iteradores que devuelven un flujo infinito o una cantidad muy grande de datos" (Functional HOWTO de Python)
Entradas pequeñas en un bucle calientecualquieraen tamaños diminutos la lista puede ser incluso ligeramente más rápida; mide
Concatenar stringsninguna — ''.join(seq)la documentación de sum() la llama la "forma preferida y rápida"

El PEP 289 también señala que "a medida que los volúmenes de datos crecen, las expresiones generadoras tienden a rendir mejor porque no agotan la memoria caché". Lo perezoso no solo es más liviano — a escala, suele ser más rápido.

El mismo truco en todas partes

sum() es solo el ejemplo estrella. Cada función integrada que reduce un iterable a un único valor acepta una expresión generadora sin paréntesis de la misma manera:

any(user.is_admin for user in users)      # short-circuits on first True
all(len(row) == 8 for row in rows)        # short-circuits on first False
max(len(line) for line in open("app.log"))

any() y all() suman una segunda ventaja: cortocircuitan. Con un generador dejan de extraer valores en el primer elemento decisivo; con una list comprehension ya pagaste por calcularlos todos.

Notas para usuarios avanzados

Recursos

Read this article in English

¿Construyendo una función con IA? Yeda AI diseña, audita y entrega sistemas LLM en producción.

Habla con nosotros · Lee el blog