Capítulo 3 de 14 9 secciones 11 min

Listas, tuplas y diccionarios

Guardar muchas cosas juntas, las rebanadas, y la copia que no se copió, que es el error más traicionero del principio.

Una lista guarda varios valores en orden y se puede modificar; una tupla es igual pero no se puede cambiar; y un diccionario guarda pares de clave y valor, que es como llega casi cualquier dato de una API. El error más común es creer que asignar una lista a otra variable la copia: no la copia, las dos etiquetas apuntan a la misma lista.

Hola! Ahora guardamos muchas cosas juntas

En el capítulo 2 cada variable guardaba un solo valor. Eso alcanza para aprender y no alcanza para trabajar 🙂

En un archivo real tienes tres mil ventas, no una. Así que necesitas estructuras que guarden muchas cosas, y en Python son tres las que vas a usar todo el tiempo.

EstructuraSe escribeSe puede cambiarPara qué
Lista[1, 2, 3]Lo que sea que tenga orden y vaya cambiando
Tupla(1, 2, 3)NoCosas que no deben cambiar: coordenadas, una fila fija
Diccionario{'ciudad': 'Lima'}Datos con nombre. Es como llega cualquier API

Listas: lo que más vas a usar

ciudades = ['Lima', 'Arequipa', 'Cusco', 'Piura', 'Trujillo']

print(ciudades[0])
print(ciudades[2])
print(len(ciudades))
Lima
Cusco
5

Se cuenta desde cero. Ya lo vimos en el capítulo 1 con el IndexError, y te lo repito porque es la fuente número uno de errores por uno de diferencia.

Ahora el atajo que te evita hacer cuentas: los índices negativos.

print(ciudades[-1])     # la ultima, siempre
print(ciudades[-2])     # la penultima
Trujillo
Piura

Nunca más ciudades[len(ciudades) - 1]. Yo escribí esa monstruosidad durante meses antes de que alguien me enseñara el -1 😅

Rebanadas, que es como se corta una lista

Los dos puntos dentro de los corchetes sacan un pedazo:

print(ciudades[0:3])     # de la 0 hasta ANTES de la 3
print(ciudades[:3])      # lo mismo, el 0 se sobreentiende
print(ciudades[2:])      # de la 2 hasta el final
print(ciudades[-2:])     # las dos ultimas
print(ciudades[::2])     # de dos en dos
['Lima', 'Arequipa', 'Cusco']
['Lima', 'Arequipa', 'Cusco']
['Cusco', 'Piura', 'Trujillo']
['Piura', 'Trujillo']
['Lima', 'Cusco', 'Trujillo']

El límite de la derecha no se incluye. Eso confunde al principio y tiene una gracia: ciudades[0:3] te da exactamente 3 elementos, y ciudades[:3] + ciudades[3:] te devuelve la lista entera sin repetir ni perder nada.

Y una que parece un truco de fiesta y sirve de verdad:

print(ciudades[::-1])
['Trujillo', 'Piura', 'Cusco', 'Arequipa', 'Lima']

Modificar una lista

ventas = [480.37, 524.90, 154.83]

ventas.append(2480.84)          # agrega al final
ventas.insert(0, 100.0)         # inserta en una posicion
print(ventas)

ultimo = ventas.pop()           # saca el ultimo Y te lo devuelve
print(ultimo, ventas)

ventas.remove(154.83)           # quita por valor, no por posicion
print(ventas)
[100.0, 480.37, 524.9, 154.83, 2480.84]
2480.84 [100.0, 480.37, 524.9, 154.83]
[100.0, 480.37, 524.9]

Ojo con remove: quita la primera aparición del valor, no todas. Y si el valor no está, revienta.

ventas.remove(999)
ValueError: list.remove(x): x not in list

Ordenar, y el error que da la primera vez

Hay dos formas de ordenar y confundirlas te deja con una variable vacía.

montos = [480.37, 154.83, 2480.84, 524.90]

ordenados = sorted(montos)          # devuelve una lista nueva
print(ordenados)
print(montos)                       # la original no se toco
[154.83, 480.37, 524.9, 2480.84]
[480.37, 154.83, 2480.84, 524.9]
montos.sort()                       # ordena la original y devuelve None
print(montos)
[154.83, 480.37, 524.9, 2480.84]

Y aquí el error clásico, que quiero que veas de frente:

resultado = montos.sort()
print(resultado)
None

Sí: None. Porque .sort() ordena la lista en el sitio y no devuelve nada. Si guardas su resultado, guardas la nada 🙃

La regla para acordarte: sorted() te da una lista nueva, .sort() modifica la que ya tienes. Cuando dudes, usa sorted(), que es más difícil de romper.

print(sorted(montos, reverse=True))
print(sorted(['Cusco', 'arequipa', 'Lima'], key=str.lower))
[2480.84, 524.9, 480.37, 154.83]
['arequipa', 'Cusco', 'Lima']

Ese key=str.lower es lo que evita que las mayúsculas se ordenen antes que todas las minúsculas, que es lo que pasa por defecto.

La copia que no se copió

Presta atención a esto, que es el error más traicionero del principio y no falla: te da un resultado equivocado sin decir nada.

original = [1, 2, 3]
copia = original          # esto NO es una copia

copia.append(4)
print('copia   :', copia)
print('original:', original)
copia   : [1, 2, 3, 4]
original: [1, 2, 3, 4]

Se modificaron las dos 😱

Acuérdate del capítulo 2: una variable es una etiqueta, no una caja. Con copia = original le pegaste una segunda etiqueta a la misma lista. No hay dos listas, hay una con dos nombres.

Se comprueba así:

print(copia is original)
True

Para copiar de verdad hay tres formas, y las tres valen:

original = [1, 2, 3]

copia_a = original.copy()
copia_b = list(original)
copia_c = original[:]        # una rebanada completa

copia_a.append(99)
print(original, copia_a)
print(copia_a is original)
[1, 2, 3] [1, 2, 3, 99]
False

Con esto te ahorro una tarde de "pero si yo no toqué esa variable". A mí me pasó con un dataframe y perdí medio día buscando el fantasma 👻

Tuplas: las que no se pueden cambiar

Una tupla es una lista que no se puede modificar. Se escribe con paréntesis.

coordenada = (-12.0464, -77.0428)      # Lima
print(coordenada[0])

coordenada[0] = 0
TypeError: 'tuple' object does not support item assignment

¿Y para qué querría algo que no puedo cambiar? Por tres razones muy concretas:

  • 🔒 Dice "esto no se toca". Un par de coordenadas, un rango de fechas fijo.
  • 🗝️ Puede ser clave de un diccionario. Una lista no puede.
  • 📦 Es lo que devuelve una función cuando devuelve varias cosas.

Ese último punto lo vas a ver todo el tiempo:

def minimo_y_maximo(numeros):
    return min(numeros), max(numeros)

bajo, alto = minimo_y_maximo([480.37, 154.83, 2480.84])
print(bajo, alto)
154.83 2480.84

Eso de repartir una tupla en varias variables se llama desempaquetado y es puro Python. Lo vamos a usar mucho en el capítulo 5.

Diccionarios: datos con nombre

Aquí está la estructura que más te va a servir en datos, porque es como llega la información del mundo real.

venta = {
    'cliente': 'C0045',
    'ciudad': 'Trujillo',
    'canal': 'Marketplace',
    'monto': 480.37,
    'unidades': 10,
}

print(venta['ciudad'])
print(venta['monto'] * 1.18)
Trujillo
566.8366

Ya no dependes de recordar que la ciudad estaba en la posición 1. Pides por nombre, que es como piensa una persona.

get, que evita que reviente

print(venta['descuento'])
KeyError: 'descuento'

Con corchetes, si la clave no existe, se cae. Con .get() te devuelve None, o lo que tú digas:

print(venta.get('descuento'))
print(venta.get('descuento', 0))
None
0

Ese segundo caso es oro puro cuando procesas datos que a veces traen un campo y a veces no, que es siempre 🌸

Recorrer un diccionario

for clave in venta:
    print(clave)
cliente
ciudad
canal
monto
unidades
for clave, valor in venta.items():
    print(f'{clave:10} {valor}')
cliente    C0045
ciudad     Trujillo
canal      Marketplace
monto      480.37
unidades   10

.items() es el que vas a usar el 90% de las veces. Los otros dos son .keys() y .values(), y se llaman igual de fácil.

Lo que de verdad te va a llegar: una lista de diccionarios

Junta las dos estructuras y tienes la forma en que viene cualquier respuesta de una API, cualquier JSON, cualquier exportación de un sistema.

ventas = [
    {'ciudad': 'Lima', 'canal': 'Web', 'monto': 480.37},
    {'ciudad': 'Cusco', 'canal': 'Tienda', 'monto': 154.83},
    {'ciudad': 'Lima', 'canal': 'WhatsApp', 'monto': 2480.84},
    {'ciudad': 'Piura', 'canal': 'Web', 'monto': 524.90},
]

total = 0
for v in ventas:
    total = total + v['monto']

print(f'Total: S/{total:,.2f}')
print(f'Ticket promedio: S/{total / len(ventas):,.2f}')
Total: S/3,640.94
Ticket promedio: S/910.24

Y ahora agrupemos por ciudad, que es la operación que más veces vas a hacer en tu vida con datos:

por_ciudad = {}

for v in ventas:
    ciudad = v['ciudad']
    por_ciudad[ciudad] = por_ciudad.get(ciudad, 0) + v['monto']

print(por_ciudad)
{'Lima': 2961.21, 'Cusco': 154.83, 'Piura': 524.9}

Guárdate ese patrón, que es un clásico: diccionario.get(clave, 0) + algo. La primera vez que aparece una ciudad, .get devuelve 0 y arranca la suma; las siguientes, sigue sumando.

En el capítulo 11 esto mismo se escribe así: df.groupby('ciudad') ['monto'].sum(). Una línea. Pero quería que vieras primero lo que hay por debajo, porque el día que groupby haga algo raro vas a saber qué está pasando 💜

Comprensiones de lista

Esta es la forma corta de crear una lista a partir de otra, y es tan de Python que se nota cuando alguien no la usa.

montos = [480.37, 154.83, 2480.84, 524.90]

# La forma larga
con_igv = []
for m in montos:
    con_igv.append(round(m * 1.18, 2))

# La forma de Python
con_igv_corto = [round(m * 1.18, 2) for m in montos]

print(con_igv)
print(con_igv == con_igv_corto)
[566.84, 182.7, 2927.39, 619.38]
True

Se lee de izquierda a derecha casi como una frase: "redondea eme por 1,18, para cada eme en montos".

Y se le puede poner una condición al final:

grandes = [m for m in montos if m > 500]
print(grandes)

ciudades = ['Lima', 'cusco', 'AREQUIPA']
print([c.title() for c in ciudades])
[2480.84, 524.9]
['Lima', 'Cusco', 'Arequipa']

Un consejo de alguien que se pasó de lista: si tu comprensión no cabe en dos líneas, escríbela como bucle normal. La gracia es que se lea mejor, no que sea más corta.

Ejercicios

1. Primero, último y cuántos

Con una lista de cinco canales, imprime el primero, el último y cuántos hay, sin contar a mano.

canales = ['Web', 'Tienda', 'WhatsApp', 'Marketplace', 'Telefono']

print(canales[0], canales[-1], len(canales))
Web Telefono 5
2. Las tres del medio

De esa misma lista, saca solo los elementos 2, 3 y 4 con una rebanada.

print(canales[1:4])
['Tienda', 'WhatsApp', 'Marketplace']

Recuerda que el límite derecho no entra: 1:4 te da las posiciones 1, 2 y 3.

3. El top 3 de montos

De una lista de montos, saca los tres más altos ordenados de mayor a menor, sin modificar la lista original.

montos = [480.37, 154.83, 2480.84, 524.90, 95.5, 1200.0]

top = sorted(montos, reverse=True)[:3]
print(top)
print(montos[0])          # la original intacta
[2480.84, 1200.0, 524.9]
480.37
4. Demuestra el aliasing

Crea una lista, hazle una copia falsa y una de verdad, y comprueba con is cuál es cuál.

pedido = ['Abarrotes', 'Bebidas', 'Snacks']
falsa = pedido
buena = pedido.copy()

falsa.append('Limpieza')
print(pedido)
print(buena)
print(falsa is pedido, buena is pedido)
['Abarrotes', 'Bebidas', 'Snacks', 'Limpieza']
['Abarrotes', 'Bebidas', 'Snacks']
True False

is pregunta si son el mismo objeto; == pregunta si tienen el mismo contenido. Dos listas distintas con lo mismo dentro dan True con == y False con is.

5. Un diccionario que no revienta

Con un diccionario de una venta que no trae la clave descuento, calcula el monto final asumiendo 0 si no está.

venta = {'cliente': 'C0325', 'monto': 524.90}

descuento = venta.get('descuento', 0)
print(f'S/{venta["monto"] * (1 - descuento):.2f}')
S/524.90

Fíjate en las comillas dobles dentro del f-string que abre con comillas simples. Si usas las mismas por fuera y por dentro, Python se pierde.

6. Contar por categoría

Con una lista de canales repetidos, cuenta cuántas veces aparece cada uno.

canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Tienda']

conteo = {}
for c in canales:
    conteo[c] = conteo.get(c, 0) + 1

print(conteo)
{'Web': 3, 'Tienda': 2, 'WhatsApp': 1}

Es el mismo patrón de la suma por ciudad, pero sumando 1 en vez del monto. Cuando lo veas escrito así vas a reconocer value_counts() de pandas sin que nadie te lo explique.

7. El máximo con nombre

De una lista de diccionarios de ventas, encuentra la de mayor monto e imprime su ciudad.

ventas = [
    {'ciudad': 'Lima', 'monto': 480.37},
    {'ciudad': 'Cusco', 'monto': 2480.84},
    {'ciudad': 'Piura', 'monto': 524.90},
]

mayor = max(ventas, key=lambda v: v['monto'])
print(mayor['ciudad'], mayor['monto'])
Cusco 2480.84

Ese lambda es una función chiquita sin nombre que dice "para comparar, mira el monto". Lo vemos con calma en el capítulo 5, tranqui.

8. Comprensión con condición

De la lista de ventas anterior, arma una lista solo con las ciudades cuyo monto pase los S/500.

print([v['ciudad'] for v in ventas if v['monto'] > 500])
['Cusco', 'Piura']
9. Quitar duplicados conservando el orden

De una lista con ciudades repetidas, saca las únicas. Hazlo de dos formas y compara.

ciudades = ['Lima', 'Cusco', 'Lima', 'Piura', 'Cusco', 'Lima']

print(sorted(set(ciudades)))

vistas = []
for c in ciudades:
    if c not in vistas:
        vistas.append(c)
print(vistas)
['Cusco', 'Lima', 'Piura']
['Lima', 'Cusco', 'Piura']

set() es rapidísimo y pierde el orden original; el bucle lo conserva. Cuál usar depende de si el orden significa algo, y muchas veces significa.

10. La tupla que no se deja

Crea una tupla con un rango de fechas e intenta cambiar el primer valor. Después haz lo mismo con una lista y compara.

rango_tupla = ('2026-01-01', '2026-12-31')
rango_lista = ['2026-01-01', '2026-12-31']

rango_lista[0] = '2026-02-01'
print(rango_lista)

rango_tupla[0] = '2026-02-01'
TypeError: 'tuple' object does not support item assignment

La tupla te protege de un cambio accidental. Cuando algo no debe moverse en todo el programa, esa protección vale más que la flexibilidad.

Lo que te llevas

  • 🔢 Se cuenta desde 0, y [-1] es siempre el último.
  • ✂️ En una rebanada, el límite de la derecha no entra.
  • ⚠️ copia = original no copia nada: usa .copy().
  • 🔀 sorted() devuelve una lista nueva; .sort() devuelve None.
  • 🗂️ Una lista de diccionarios es como llega el mundo real, y agrupar con .get(clave, 0) es el patrón que después hace groupby.

En el capítulo 4 le enseñamos a Python a decidir y a repetir: if, for y while. Ahí es donde esto deja de ser una calculadora y empieza a ser un programa.

Que tengas un hermoso día! 🌟

¿Tienes alguna duda o consulta?