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ón | Usa | Por qué |
|---|---|---|
Reducir a un solo valor (sum, max, min, any, all) | expresión generadora | el consumidor extrae valores de forma perezosa; no hace falta lista |
Necesitas los valores otra vez (indexar, len, dos pasadas) | list comprehension | un generador es de un solo uso — una vez consumido, queda vacío |
| Entradas muy grandes o flujos sin límite | expresión generadora | las 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 caliente | cualquiera | en tamaños diminutos la lista puede ser incluso ligeramente más rápida; mide |
| Concatenar strings | ninguna — ''.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
- Los generadores son de un solo uso. Después de
sum(g), el generadorgqueda agotado; iterarlo de nuevo no produce nada. Si dos consumidores necesitan los datos, construye la lista una vez a propósito. - El iterable de más a la izquierda se evalúa de inmediato. La referencia del lenguaje es explícita: el iterable de la cláusula
formás a la izquierda se ejecuta inmediatamente al definir la expresión — así que unNameErroro un nombre de archivo inválido falla al definirla, no en la primera extracción. Todo lo demás es perezoso. - Los floats merecen
math.fsum(). Para sumar valores de punto flotante con precisión extendida, la documentación desum()remite amath.fsum()— que también acepta una expresión generadora sin paréntesis. - ¿Encadenar iterables? No hagas
sum(list_of_lists, [])— la documentación recomiendaitertools.chain(), que además es perezoso. - Cuidado con los flujos infinitos.
max()/min()sobre un generador sin límite nunca retornan (Functional HOWTO). La pereza mueve el costo; no lo acota.
Recursos
- Función integrada
sum()— documentación de Python - PEP 289 — Generator Expressions
- Expresiones generadoras — referencia del lenguaje Python
- Functional Programming HOWTO — expresiones generadoras vs list comprehensions
math.fsum()— suma precisa de floats
¿Construyendo una función con IA? Yeda AI diseña, audita y entrega sistemas LLM en producción.