En esta página
Comprensiones y generadores
Comprensiones de lista
Las comprensiones de lista son una forma concisa y Pythónica de crear listas aplicando una expresión a cada elemento de un iterable. La sintaxis básica es:
[expresión for elemento in iterable]Equivalen a un bucle for que hace append, pero son más legibles y en muchos casos más rápidas:
# Forma tradicional con bucle for
cuadrados_loop = []
for x in range(1, 11):
cuadrados_loop.append(x ** 2)
# Con comprensión de lista — más concisa
cuadrados = [x ** 2 for x in range(1, 11)]
print(cuadrados) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Aplicar transformaciones a strings
nombres = ["ana", "bruno", "carmen", "diego"]
capitalizados = [nombre.capitalize() for nombre in nombres]
print(capitalizados) # ['Ana', 'Bruno', 'Carmen', 'Diego']
# Extraer valores de estructuras anidadas
personas = [
{"nombre": "Ana", "edad": 25},
{"nombre": "Bruno", "edad": 30},
{"nombre": "Carmen", "edad": 28}
]
edades = [p["edad"] for p in personas]
print(edades) # [25, 30, 28]
# Con llamadas a función
import math
raices = [math.sqrt(x) for x in range(1, 6)]
print(raices) # [1.0, 1.414..., 1.732..., 2.0, 2.236...]Comprensiones con condición (filtrado)
Puedes agregar una cláusula if al final para filtrar elementos:
# Solo los pares
pares = [x for x in range(1, 21) if x % 2 == 0]
print(pares) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Solo los que cumplen múltiples condiciones
divisibles = [x for x in range(1, 101) if x % 3 == 0 and x % 5 == 0]
print(divisibles) # [15, 30, 45, 60, 75, 90]
# Filtrar y transformar simultáneamente
productos = [
{"nombre": "Laptop", "precio": 1299.99, "disponible": True},
{"nombre": "Mouse", "precio": 29.99, "disponible": False},
{"nombre": "Teclado", "precio": 89.99, "disponible": True},
{"nombre": "Monitor", "precio": 399.99, "disponible": True},
]
# Solo productos disponibles y su nombre en mayúsculas
disponibles = [p["nombre"].upper() for p in productos if p["disponible"]]
print(disponibles) # ['LAPTOP', 'TECLADO', 'MONITOR']
# Filtrar con isinstance()
datos_mixtos = [1, "dos", 3, "cuatro", 5, None, 7]
solo_enteros = [x for x in datos_mixtos if isinstance(x, int)]
print(solo_enteros) # [1, 3, 5, 7]Comprensión con if-else (expresión ternaria)
Cuando necesitas transformar elementos de manera diferente según una condición, usas la expresión ternaria dentro de la expresión (no al final):
# Clasificar números como par o impar
clasificados = ["par" if x % 2 == 0 else "impar" for x in range(1, 8)]
print(clasificados) # ['impar', 'par', 'impar', 'par', 'impar', 'par', 'impar']
# Normalizar calificaciones
notas = [45, 78, 92, 55, 88, 30, 67]
aprobados = ["✓" if nota >= 60 else "✗" for nota in notas]
print(aprobados) # ['✗', '✓', '✓', '✗', '✓', '✗', '✓']
# Reemplazar None con valor por defecto
valores = [1, None, 3, None, 5]
sin_none = [v if v is not None else 0 for v in valores]
print(sin_none) # [1, 0, 3, 0, 5]Comprensiones anidadas
Las comprensiones pueden contener múltiples cláusulas for, lo que equivale a bucles anidados. El orden es el mismo que en los bucles anidados: de exterior a interior.
# Tabla de multiplicar
tabla = [(x, y, x * y) for x in range(1, 4) for y in range(1, 4)]
for a, b, resultado in tabla:
print(f"{a} × {b} = {resultado}")
# Aplanar una lista de listas
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
plana = [elemento for fila in matriz for elemento in fila]
print(plana) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Generar combinaciones (producto cartesiano)
colores = ["rojo", "verde"]
tamaños = ["S", "M", "L"]
combinaciones = [(color, tamaño) for color in colores for tamaño in tamaños]
print(combinaciones)
# [('rojo', 'S'), ('rojo', 'M'), ('rojo', 'L'), ('verde', 'S'), ('verde', 'M'), ('verde', 'L')]
# Crear matriz con comprensión anidada
ceros_3x3 = [[0] * 3 for _ in range(3)]
print(ceros_3x3) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# ¡Trampa! No hagas esto — todas las filas serían el mismo objeto
# ceros_mal = [[0] * 3] * 3 # ¡MAL! Comparten referenciaComprensiones de diccionario
Similar a las comprensiones de lista, pero crean diccionarios con la sintaxis {clave: valor for ... in ...}:
# Cuadrados de los primeros 5 números
cuadrados_dict = {x: x ** 2 for x in range(1, 6)}
print(cuadrados_dict) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Invertir un diccionario (clave ↔ valor)
original = {"a": 1, "b": 2, "c": 3}
invertido = {v: k for k, v in original.items()}
print(invertido) # {1: 'a', 2: 'b', 3: 'c'}
# Filtrar entradas de un diccionario
precios = {"manzana": 1.2, "pera": 2.5, "uva": 3.8, "naranja": 1.8}
baratos = {fruta: precio for fruta, precio in precios.items() if precio < 2.0}
print(baratos) # {'manzana': 1.2, 'naranja': 1.8}
# Transformar valores
precios_con_iva = {producto: round(precio * 1.21, 2)
for producto, precio in precios.items()}
print(precios_con_iva) # {'manzana': 1.45, 'pera': 3.025, ...}
# Desde dos listas
claves = ["nombre", "edad", "ciudad"]
valores = ["María", 29, "Lima"]
perfil = {k: v for k, v in zip(claves, valores)}
print(perfil) # {'nombre': 'María', 'edad': 29, 'ciudad': 'Lima'}Comprensiones de conjunto
La misma lógica pero crea sets (elementos únicos):
# Set de cuadrados
cuadrados_set = {x ** 2 for x in range(-5, 6)}
print(cuadrados_set) # {0, 1, 4, 9, 16, 25} — sin duplicados
# Extraer letras únicas de una frase
frase = "la programación en python es poderosa"
letras_únicas = {letra for letra in frase if letra.isalpha()}
print(sorted(letras_únicas))
# Dominos únicos de una lista de emails
emails = ["[email protected]", "[email protected]", "[email protected]", "[email protected]"]
dominios = {email.split("@")[1] for email in emails}
print(dominios) # {'gmail.com', 'yahoo.com', 'outlook.com'}Generadores con yield
Un generador es una función que usa la palabra clave yield para devolver valores uno a la vez. Cuando Python ejecuta yield, la función pausa y guarda su estado completo hasta la próxima llamada:
def contar_hasta(n: int):
"""Generador que cuenta de 1 a n."""
print("Inicio del generador")
i = 1
while i <= n:
print(f" Antes de yield {i}")
yield i
print(f" Después de yield {i}")
i += 1
print("Fin del generador")
gen = contar_hasta(3)
print(type(gen)) # <class 'generator'>
# Llamar next() manualmente
print(next(gen)) # Inicio del generador / Antes de yield 1 / devuelve 1
print(next(gen)) # Después de yield 1 / Antes de yield 2 / devuelve 2
print(next(gen)) # Después de yield 2 / Antes de yield 3 / devuelve 3
# Próxima llamada lanza StopIteration
try:
next(gen)
except StopIteration:
print("Generador exhausto")Generadores para secuencias infinitas
def enteros_positivos():
"""Genera enteros positivos infinitamente."""
n = 1
while True:
yield n
n += 1
def primos():
"""Genera números primos infinitamente usando criba."""
def es_primo(n: int) -> bool:
if n < 2:
return False
return all(n % i != 0 for i in range(2, int(n**0.5) + 1))
n = 2
while True:
if es_primo(n):
yield n
n += 1
# Tomar los primeros 10 primos
from itertools import islice
primeros_10_primos = list(islice(primos(), 10))
print(primeros_10_primos) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]yield from — delegar a otro iterable
def cadena(*iterables):
"""Encadena múltiples iterables como un generador."""
for iterable in iterables:
yield from iterable # Delega al iterable
resultado = list(cadena([1, 2], "AB", (3, 4)))
print(resultado) # [1, 2, 'A', 'B', 3, 4]Expresiones generadoras
Una expresión generadora es como una comprensión de lista pero con paréntesis. No crea la lista completa en memoria:
# Lista — crea TODO en memoria
lista = [x ** 2 for x in range(1_000_000)]
# Expresión generadora — perezosa, usa O(1) memoria adicional
gen_exp = (x ** 2 for x in range(1_000_000))
# En funciones que aceptan iterables, no necesitas los paréntesis extra
total = sum(x ** 2 for x in range(100)) # Un solo par de paréntesis
print(total) # 328350
maximo = max(len(p["nombre"]) for p in personas)
print(maximo)
# any() y all() con generadores (cortocircuito eficiente)
tiene_adulto = any(p["edad"] >= 18 for p in personas)
todos_adultos = all(p["edad"] >= 18 for p in personas)Comparación de rendimiento
import sys
import time
n = 100_000
# Lista — mucha memoria
lista = [x ** 2 for x in range(n)]
print(f"Tamaño lista: {sys.getsizeof(lista):,} bytes")
# Generador — memoria mínima
gen = (x ** 2 for x in range(n))
print(f"Tamaño generador: {sys.getsizeof(gen)} bytes")
# Para suma, el generador es equivalente en velocidad pero usa mucho menos RAM
inicio = time.perf_counter()
suma_lista = sum([x ** 2 for x in range(n)])
t_lista = time.perf_counter() - inicio
inicio = time.perf_counter()
suma_gen = sum(x ** 2 for x in range(n))
t_gen = time.perf_counter() - inicio
print(f"Suma lista: {suma_lista} en {t_lista:.4f}s")
print(f"Suma gen: {suma_gen} en {t_gen:.4f}s")Resumen
Las comprensiones de lista, diccionario y conjunto son la forma idiomática de transformar y filtrar datos en Python. Los generadores con yield y las expresiones generadoras son la solución perfecta para procesar grandes cantidades de datos sin cargar todo en memoria. En la próxima lección aprenderemos a manejar errores de manera robusta con try/except.
Inicia sesión para guardar tu progreso