Listas — muchos valores bajo un nombre
Guardar tantos valores como quieras bajo un nombre, llegar dentro con un índice y un slice, los métodos que cambian una lista, y la trampa de que dos nombres acaben pegados a la misma.
¿Cómo quieres aprender?
El problema que vamos a resolver
Una tienda necesita guardar los precios de tres productos. Con lo que hemos aprendido hasta ahora solo hay una forma de hacerlo:
price_1 = 15
price_2 = 60
price_3 = 850
print(price_1 + price_2 + price_3)925Ahora la pregunta: ¿y si son cuarenta productos? Cuarenta variables. Y una línea de aritmética con cuarenta trozos. Y cuando la tienda empiece a vender algo nuevo, tendrás que modificar el código e inventar otra variable.
El problema no es solo el tecleo, es la forma. Para Python, price_1, price_2 y price_3 son tres nombres completamente sin relación —tan sin relación como name y age—. Tú sabes que son tres instancias de la misma cosa. Simplemente no teníamos aún forma de escribir "muchas de la misma cosa" en código.
Una lista es esa forma. Un nombre, tantos valores como quieras bajo él, en orden.
Al terminar este capítulo podrás
- Construir una lista y llegar dentro de ella con un índice y un slice
- Cambiar una lista con
append,insert,removeypop - Explicar por qué una lista se puede cambiar cuando un trozo de texto no
- Reconocer y evitar que
b = adeje dos nombres pegados a una lista - Leer un
IndexErrory unValueErrory decir qué falló
Requisitos previos: bucles for y range.
Crear una lista
Corchetes, con los valores separados por comas:
prices = [15, 60, 850]
print(prices)
print(len(prices))[15, 60, 850]
3len() funciona aquí exactamente igual que con el texto —tres valores, así que 3—.
Imprimir muestra la lista entera, corchetes y todo. Eso no es para que lo lea un usuario, es para que lo entiendas tú: [15, 60, 850] te dice que esto es una lista, no 925.
Llegar dentro
El indexado funciona exactamente igual que con las cadenas —contando desde cero, negativos desde atrás—:
prices = [15, 60, 850]
print(prices[0])
print(prices[-1])
print(prices[1:])15
850
[60, 850]Las tres líneas son las reglas del capítulo seis, sin cambios. Una diferencia merece la pena notar: prices[0] devuelve un número, mientras que prices[1:] devuelve una lista. Un slice siempre devuelve el mismo tipo del original, nunca un solo elemento.
Y aquí está la diferencia de verdad: una lista se puede cambiar
En el capítulo seis vimos que un carácter dentro de un texto no se puede reemplazar:
name = "rafi"
name[0] = "R"TypeError: 'str' object does not support item assignmentLa misma línea funciona en una lista:
prices = [15, 60, 850]
prices[0] = 20
print(prices)[20, 60, 850]No se creó ninguna lista nueva —esta lista cambió. Esa es la propiedad más importante que tiene una lista, y la trampa del final de este capítulo sale directamente de ella.
Las palabras para esto: el texto es inmutable, una lista es mutable.
Añadir y quitar
stock = ["pen", "notebook"]
stock.append("bag")
stock.insert(1, "eraser")
print(stock)
stock.remove("pen")
print(stock)
last = stock.pop()
print(last)
print(stock)['pen', 'eraser', 'notebook', 'bag']
['eraser', 'notebook', 'bag']
bag
['eraser', 'notebook']Cuatro métodos, cuatro trabajos distintos:
append(x)— añade al finalinsert(i, x)— lo pone en la posicióni, desplazando lo que viene después a la derecharemove(x)— busca el primer elemento con ese valor y lo quitapop()— quita el último elemento y te lo devuelve
La diferencia entre esos dos últimos es donde surge la mayor confusión: remove quiere saber qué quitar, pop quiere saber de dónde. Y pop te da la cosa que quitó; remove no.
Estos métodos no devuelven la lista. Con el texto tenías que guardar el resultado dename.upper(), porque construía texto nuevo y te lo devolvía. Las listas son al revés:appendcambia la lista misma y devuelveNone.
De ahí viene el error clásico:
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)NoneLa lista se perdió. Escribe stock.append("bag"), no stock = stock.append("bag").
Buscar y recorrer
stock = ["pen", "notebook", "bag"]
print("pen" in stock)
print("chair" in stock)
for item in stock:
print(item.upper())True
False
PEN
NOTEBOOK
BAGin buscaba dentro de un texto, y busca dentro de una lista también —la misma palabra haciendo el mismo trabajo—.
Y for item in stock recorre los valores de uno en uno. Fíjate que no hay range ni aritmética de índices, exactamente igual que cuando recorríamos los caracteres de una palabra en el capítulo once.
Cuando necesitas la posición y el valor, enumerate:
stock = ["pen", "notebook"]
for position, item in enumerate(stock, start=1):
print(position, item)1 pen
2 notebookSin start=1 la cuenta empieza en cero. Una lista que se muestra a una persona suele querer empezar en uno.
La trampa que pilla a todo el mundo
En el capítulo cuatro vimos esto:
a = 3
b = a
b = b + 1
print(a, b)3 4b recibió un valor y no estaba vigilando a. Ahora haz lo mismo con una lista:
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)[1, 2, 3, 4]
[1, 2, 3, 4]Se cambió b, y a cambió también.
La razón no contradice la regla anterior, es la regla anterior. En la línea b = a, b recibió exactamente lo que tenía a —y lo que tenía a era dónde está la lista, no una copia de la lista. Así que ahora los dos nombres apuntan a la misma lista. b.append(4) cambió esa única lista, y los dos nombres muestran el mismo cambio.
Nunca ves esto con los números, porque b = b + 1 no modifica el número viejo —hace uno nuevo y lo pone en b—. Los números son seguros precisamente porque no se pueden cambiar; las listas sí se pueden cambiar, así que no son seguras.
Si quieres una copia separada, tienes que pedirla:
a = [1, 2, 3]
b = a.copy()
b.append(4)
print(a)
print(b)[1, 2, 3]
[1, 2, 3, 4]Cuándo tener cuidado. Cada vez que pones una lista bajo un segundo nombre, o pasas una lista a una función (capítulo diecinueve), pregúntate una vez: ¿quiero cambiar esta, o quiero una copia? La gente que nunca se hace esa pregunta pasa días cazando bugs que leen como "pero yo no he cambiado nada aquí".
Un ejemplo completo
shopping.py:
# A shopping list, built up and then summarised
items = []
items.append("pen")
items.append("notebook")
items.append("bag")
items.insert(0, "eraser")
print("List :", items)
print("Count :", len(items))
print("First :", items[0])
print("Last :", items[-1])
print("Has bag :", "bag" in items)
items.remove("pen")
dropped = items.pop()
print()
print("After removing 'pen' and popping the last one:")
print("List :", items)
print("Dropped :", dropped)
print("Count :", len(items))
print()
print("Numbered:")
for position, item in enumerate(items, start=1):
print(f" {position}. {item}")List : ['eraser', 'pen', 'notebook', 'bag']
Count : 4
First : eraser
Last : bag
Has bag : True
After removing 'pen' and popping the last one:
List : ['eraser', 'notebook']
Dropped : bag
Count : 2
Numbered:
1. eraser
2. notebookTres cosas que merece la pena notar.
Empieza de una lista vacía. La línea items = [] hace el mismo trabajo que hacía un acumulador fuera de un bucle en el capítulo diez —donde estaba total = 0, ahora está []—. Necesitas un sitio donde ir recogiendo antes de empezar a recoger.
insert(0, "eraser") lo puso al principio, así que eraser sale primero en la lista impresa. El orden en que se añadieron las cosas no es el orden en que acaban.
pop() te devolvió lo que quitó, así que dropped guarda bag. remove("pen") no devolvió nada, así que el pen quitado simplemente se fue.
Cuando falla
IndexError: list index out of range Un índice que supera el número de valores en la lista. En una lista de tres los índices válidos son 0, 1, 2 —no hay prices[3]—. Para el último valor, prices[-1] es lo más seguro que puedes escribir, porque no necesita saber la longitud.
ValueError: list.remove(x): x not in list El valor que pediste quitar no está. Comprueba con if x in stock: primero, o usa try del capítulo veinticuatro.
AttributeError: 'NoneType' object has no attribute 'append' Anteriormente se escribió stock = stock.append(...), así que stock ahora es None. No guardes el resultado —stock.append(...) por sí sola es toda la operación.
Cambié una lista y otra también cambió Dos nombres están pegados a una lista. Escribe b = a.copy() en vez de b = a.
Estoy quitando dentro de un bucle y se saltan cosas
numbers = [1, 2, 2, 3, 4]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers)[1, 2, 3]Un 2 sobrevivió. Acortar la lista que estás recorriendo desincroniza el contador interno de Python con las posiciones de la lista, así que algunos valores se saltan. La solución no es quitar, sino recoger lo que quieres guardar en una lista nueva.
numbers = [1, 2, 2, 3, 4]
kept = []
for n in numbers:
if n % 2 != 0:
kept.append(n)
print(kept)[1, 3]Puse a una variable list y ahora list(...) no funciona list es uno de los nombres integrados de Python. Usa uno con sentido —items, prices, names.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
El resultado de append se guarda de nuevo en stock. ¿Qué se imprime?
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)- ANone
- B['pen', 'notebook', 'bag']
- C['bag']
- DUn `AttributeError`
Solo se cambió b. ¿Qué imprimen las dos líneas?
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)- A[1, 2, 3] [4]
- B[1, 2, 3] [1, 2, 3, 4]
- C[1, 2, 3, 4] [1, 2, 3]
- D[1, 2, 3, 4] [1, 2, 3, 4]
La lista tiene tres valores y se pide el índice tres. ¿Qué pasa?
prices = [15, 60, 850]
print(prices[3])- AUna lista vacía
- BUn `IndexError` — con tres valores los índices válidos son `0`, `1`, `2`
- CNone
- D850 —el último valor
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Escribe un archivo llamado marks.py que contenga una lista de al menos seis notas.
Luego imprime:
- La lista entera, y cuántas notas hay en ella
- La primera y la última nota —usa
[-1]para la última - El total y la media, acumulados con un bucle en vez de tecleados a mano
- Una lista numerada usando
enumerate, empezando en uno - Una lista nueva de las notas aprobadas (40 o más), y cuántas hay
Luego haz dos experimentos, y los dos importan:
appendotra nota al final e imprime el total de nuevo. ¿Tuviste que cambiar la línea que lo calcula? Si no, escribiste un cálculo de verdad.- Escribe
backup = marks, luegomarks.append(100), e imprime las dos. ¿Qué hay enbackup? Ahora haz lo mismo con.copy()y mira la diferencia.
Ese último experimento son los tres minutos más valiosos de este capítulo. Verlo una vez con tus propios ojos significa no gastar una tarde en ello después.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz