Una vez que las comprensiones de listas hacen clic, nunca más escribirás un bucle for que añada a una lista.
No son solo azúcar sintáctico — señalan la intención claramente y se ejecutan más rápido que el bucle equivalente en CPython
gracias a la optimización del bytecode. Según el
tutorial oficial de Python,
las comprensiones proporcionan una forma concisa de crear listas basadas en secuencias existentes u otros iterables.
Este artículo construye el modelo mental primero, luego cubre cada patrón real: filtrado, anidamiento,
comprensiones de dicts y sets, y el único caso donde deberías usar un bucle for normal en su lugar.
El patrón básico
La anatomía de una comprensión de lista es [expression for item in iterable].
Tres partes: la expresión de salida (en qué se convierte cada elemento), la variable de bucle,
y el iterable del que extraer. Empieza con un bucle familiar y colápsalo.
# Plain for loop — building a list of file sizes in KB
file_sizes_bytes = [1024, 204800, 51200, 3145728, 8192]
sizes_kb = []
for size in file_sizes_bytes:
sizes_kb.append(size / 1024)
# print(sizes_kb) → [1.0, 200.0, 50.0, 3072.0, 8.0]
# Same result as a list comprehension — one line, same meaning
sizes_kb = [size / 1024 for size in file_sizes_bytes]La comprensión se lee casi como inglés: "dame size / 1024 para cada size
en file_sizes_bytes." Esa claridad es la verdadera ventaja — un lector no tiene que rastrear una
llamada append para entender qué estás construyendo.
# Another common pattern: deriving one list from another
usernames = ["alice", "bob", "carol"]
# Build a list of display names
display_names = [name.capitalize() for name in usernames]
# ['Alice', 'Bob', 'Carol']
# Or extract a single field from a list of dicts
users = [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "viewer"},
{"id": 3, "name": "Carol", "role": "editor"},
]
names = [user["name"] for user in users]
# ['Alice', 'Bob', 'Carol']Filtrado con if
Agrega una condición al final y solo los elementos que pasan entran en la lista de salida:
[expression for item in iterable if condition].
Este es el patrón que reemplaza una combinación for + if + append.
# Loop version — extract only active users
active_users = []
for user in users:
if user["active"]:
active_users.append(user["name"])
# Comprehension version — identical result
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
{"name": "Dave", "active": False},
]
active_users = [user["name"] for user in users if user["active"]]
# ['Alice', 'Carol']# Filtering a raw CSV row — drop blanks and whitespace-only values
raw_row = ["[email protected]", "", " ", "editor", " "]
clean_row = [field.strip() for field in raw_row if field.strip()]
# ['[email protected]', 'editor']
# Filtering a list of log levels
log_lines = [
"INFO server started",
"DEBUG loading config",
"ERROR database timeout",
"DEBUG query took 450ms",
"ERROR disk space low",
]
errors = [line for line in log_lines if line.startswith("ERROR")]
# ['ERROR database timeout', 'ERROR disk space low']Trabajando con cadenas
El procesamiento de cadenas es donde las comprensiones muestran su valor. La combinación de los ricos métodos de cadenas de Python y la sintaxis de comprensión mantiene las tuberías de transformación legibles sin variables intermedias.
# Strip whitespace from tags coming out of a form field
raw_tags = [" python ", "data science", " machine-learning ", "API"]
tags = [tag.strip().lower() for tag in raw_tags]
# ['python', 'data science', 'machine-learning', 'api']
# Normalise email addresses from a signup CSV
raw_emails = ["[email protected]", " [email protected] ", "[email protected]"]
emails = [e.strip().lower() for e in raw_emails]
# ['[email protected]', '[email protected]', '[email protected]']
# Extract file extensions from a list of uploaded filenames
filenames = ["report.pdf", "avatar.PNG", "data.CSV", "archive.tar.gz", "notes.txt"]
extensions = [name.rsplit(".", 1)[-1].lower() for name in filenames if "." in name]
# ['pdf', 'png', 'csv', 'gz', 'txt']" [email protected] " pasa una verificación .lower() en el contenido pero aún lleva
espacios iniciales que romperán una búsqueda en base de datos o coincidencia de clave JSON.Comprensiones de dicts y sets
La misma idea se extiende a dicts y sets. Una
comprensión de dict
usa llaves con un par key: value: {key: value for item in iterable}.
Una comprensión de set elimina los dos puntos y produce una colección deduplicada:
{expression for item in iterable}.
# Invert a dict — swap keys and values
permissions = {"alice": "admin", "bob": "viewer", "carol": "editor"}
by_role = {role: name for name, role in permissions.items()}
# {'admin': 'alice', 'viewer': 'bob', 'editor': 'carol'}
# Build a fast lookup dict from a list of user records
users = [
{"id": 101, "name": "Alice", "active": True},
{"id": 102, "name": "Bob", "active": False},
{"id": 103, "name": "Carol", "active": True},
]
# O(1) lookups by ID — much faster than scanning the list every time
user_by_id = {user["id"]: user for user in users}
# {101: {...}, 102: {...}, 103: {...}}
# Access a user directly
user_by_id[102]["name"] # 'Bob'# Set comprehension — deduplicate a list of file extensions
uploads = ["report.pdf", "data.csv", "summary.pdf", "export.CSV", "notes.txt"]
unique_extensions = {name.rsplit(".", 1)[-1].lower() for name in uploads if "." in name}
# {'pdf', 'csv', 'txt'} — order not guaranteedUna advertencia con los sets: el orden no se preserva. Si necesitas deduplicar una lista mientras
mantienes el orden original, una comprensión de set es la herramienta incorrecta — usa
list(dict.fromkeys(items)) en su lugar, que aprovecha el comportamiento de orden de inserción de
los dicts en Python 3.7+.
Comprensiones anidadas
Puedes anidar comprensiones para iterar sobre estructuras anidadas. El caso de uso más común es aplanar una lista de listas — una matriz de un análisis CSV, páginas de respuesta API fragmentadas, o resultados de consulta agrupados.
# Flatten a 2D list (e.g. paginated API results)
pages = [
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}],
[{"id": 3, "name": "Carol"}],
[{"id": 4, "name": "Dave"}, {"id": 5, "name": "Eve"}],
]
all_users = [user for page in pages for user in page]
# [{'id': 1, ...}, {'id': 2, ...}, {'id': 3, ...}, {'id': 4, ...}, {'id': 5, ...}]
# The order mirrors what nested for loops would produce:
# for page in pages:
# for user in page:
# all_users.append(user)Lee las comprensiones anidadas de izquierda a derecha — el bucle externo viene primero, el bucle interno segundo.
Eso coincide con el orden de los bucles for anidados.
# Two levels — fine and readable
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Three levels — stop here. Use a plain loop or a helper function.
# This is the point where readability dies:
cube = [[[1,2],[3,4]],[[5,6],[7,8]]]
# Don't do this:
flat3 = [val for layer in cube for row in layer for val in row]
# Do this instead:
flat3 = []
for layer in cube:
for row in layer:
flat3.extend(row)(user["id"] for user in users). Se evalúan de forma perezosa — los elementos se producen uno a la vez
en lugar de construir la lista completa en memoria. Úsalas cuando solo necesites iterar una vez, o cuando estés
pasando el resultado directamente a sum(), max(), any(), o funciones similares.
Consulta la referencia de expresiones generadoras para todos los detalles.El ternario if/else dentro de una comprensión
Cuando necesitas transformar elementos en uno de dos valores — en lugar de eliminarlos — usa una expresión ternaria en la posición de salida. La posición importa: el ternario pertenece al inicio, no después del iterable.
# Correct: ternary is the output expression
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
]
status = ["active" if user["active"] else "inactive" for user in users]
# ['active', 'inactive', 'active']
# Normalise a config value — replace None with a default
raw_config = ["/var/log", None, "/tmp", None, "/etc/app"]
paths = [path if path is not None else "/var/log/default" for path in raw_config]
# ['/var/log', '/var/log/default', '/tmp', '/var/log/default', '/etc/app']# Common mistake — putting the ternary after the iterable (SyntaxError)
# status = ["active" for user in users if user["active"] else "inactive"] # WRONG
# The trailing "if" is a filter — it drops non-matching items entirely.
# The ternary ("if ... else ...") in the output expression transforms all items.
# They serve different purposes. You can combine them:
# Keep only users, but show their status
status = ["active" if user["active"] else "inactive"
for user in users
if user["name"] != "Bob"]
# ['active', 'active'] — Bob was filtered out entirelyCuándo usar un bucle for en su lugar
Las comprensiones de listas son para construir listas. Si lo que realmente estás haciendo es ejecutar
efectos secundarios, usa un bucle for — no una comprensión. Esto importa más allá del estilo: una comprensión
que descarta su resultado desperdicia memoria construyendo una lista que nadie usa, y oculta la intención al lector.
# Bad — comprehension for side effects (writing to a file, printing, calling an API)
[print(f"Processing user {user['name']}") for user in users] # don't do this
[requests.post("/api/notify", json=user) for user in users] # definitely don't do this
# Good — plain for loop makes the intent obvious
for user in users:
print(f"Processing user {user['name']}")
for user in users:
requests.post("/api/notify", json=user)# Bad — comprehension with logic complex enough to need comments or multiple steps
result = [
user["name"].strip().lower()
if user.get("active") and user.get("email_verified")
else user["name"].strip().lower() + " (unverified)"
for user in users
if user.get("role") in ("admin", "editor") and user.get("last_login") is not None
]
# Good — break it out when the logic is this involved
result = []
for user in users:
if user.get("role") not in ("admin", "editor"):
continue
if user.get("last_login") is None:
continue
name = user["name"].strip().lower()
if not (user.get("active") and user.get("email_verified")):
name += " (unverified)"
result.append(name)- Usa una comprensión cuando estés construyendo una nueva lista a partir de un iterable existente con una expresión clara y un filtro opcional.
- Usa un bucle for cuando estés ejecutando efectos secundarios — E/S, llamadas de red, impresión, mutación de estado externo.
- Usa un bucle for cuando la lógica de transformación necesite múltiples líneas, variables intermedias, o comentarios para ser entendida.
- Usa una expresión generadora cuando solo necesites iterar una vez o pasar directamente a
sum(),any(),max()— no se necesita lista en memoria.
Nota de rendimiento
Las comprensiones de listas son significativamente más rápidas que un bucle for + append equivalente
en CPython. La razón es el bytecode: una comprensión compila a un opcode LIST_APPEND dedicado
que evita la búsqueda de atributos en list.append en cada iteración.
El wiki de consejos de rendimiento de Python
cubre esto, y la diferencia es típicamente del 10 al 40% para cargas de trabajo de Python puro dependiendo del tamaño de la lista.
import timeit
data = list(range(100_000))
# for + append
def with_loop():
result = []
for x in data:
result.append(x * 2)
return result
# list comprehension
def with_comprehension():
return [x * 2 for x in data]
# generator expression — no list built at all
def with_generator():
return sum(x * 2 for x in data)
# Typical results on CPython 3.12:
# with_loop(): ~7.2 ms
# with_comprehension(): ~4.8 ms (~33% faster)
# with_generator(): ~4.1 ms (and uses O(1) memory vs O(n))Si no necesitas una lista materializada — estás pasando el resultado a sum(),
any(), max(), o iterándola una vez — usa una
expresión generadora
en su lugar. Usa memoria constante independientemente del tamaño de entrada, lo que importa al procesar grandes exportaciones CSV
o cargas útiles JSON en un bucle cerrado.
Conclusión
Las comprensiones de listas son una de esas características de Python que se sienten torpes durante más o menos una semana y luego se vuelven imposibles de vivir sin ellas. El modelo mental es simple: expresión de salida, variable de bucle, iterable, filtro opcional. Atente a eso y escribirás Python legible e idiomático. Cuando la lógica se complica, esa es la señal para volver a un bucle normal — no es un fracaso, solo la herramienta correcta para el trabajo.
Si trabajas con datos JSON en Python — convirtiendo respuestas de API en listas de valores, extrayendo campos de registros, construyendo dicts de búsqueda — las herramientas en este sitio combinan bien con lo que acabas de aprender. Prueba el Formateador JSON para inspeccionar y formatear cargas JSON antes de escribir la comprensión que las procesa, o el Formateador CSV para validar datos CSV antes de analizarlos en una lista de filas. Para la referencia completa del lenguaje, la documentación de Python sobre comprensiones de listas y PEP 202 (la propuesta original) valen la pena leerlos.