Fundamentos
Listas en Python: crear, acceder, modificar y recorrer
Aprende listas en Python: crear, acceder por índice y rebanada, modificar con append y pop, entender las copias y recorrer con for, enumerate y while.
Por Prof. Rodrigo Faustino · Actualizado el · 18 min de lectura
Métricas de este artículo
- Palabras
- 3453
- Caracteres
- 18.774
- Tiempo de lectura
- ~18 min
Una lista en Python es una colección ordenada de valores guardados bajo un único nombre. Se escribe entre corchetes, con los elementos separados por coma, cada elemento tiene una posición numerada a partir de 0, y el programa puede consultar, cambiar, agregar y quitar elementos sin crear una variable nueva para cada uno. Este es el primero de los tipos de datos que guardan varios valores, y casi todo programa que pasa de unas pocas líneas acaba usando uno.
Este artículo abre el módulo de estructuras de datos. Aquí veremos cómo crear una lista, cómo leer una posición o un pedazo de ella, cómo modificarla, por qué b = a no hace una copia, las maneras de recorrer los elementos y los errores que aparecen en casi todo grupo. Al final hay dos usos en la ESP32: una ventana de lecturas de un sensor y un conjunto de LED guardados en una lista.
Por qué una lista
Imagina un programa que lee las notas de cinco alumnos y calcula el promedio. Sin listas, el camino es crear nota1, nota2, nota3, nota4 y nota5, y repetir cada cuenta cinco veces. Para veinte alumnos, son veinte variables. Para un número que solo se conoce en el momento de la ejecución, no hay cómo escribir el programa.
Con una lista, el programa tiene un solo nombre, notas, y el for que ya conoces pasa por cada elemento. El tamaño de la lista puede cambiar durante la ejecución, y el código que suma, busca o cuenta sigue siendo el mismo.
Lo mismo vale para un sensor. Una lectura aislada oscila, y para saber si la temperatura está subiendo hay que comparar varias lecturas. Guardar las últimas diez en una lista es el camino natural, y es lo que hace el ejemplo de la ESP32 más abajo.
Del programa al semestre planificado
- Plan de enseñanza y calendario montados solos
- Diario de clases listo para pegar en el SIGA
- Diapositivas y listas de ejercicios con solucionario
Versión Community gratis · Windows
Crear una lista
La forma más común es escribir los elementos entre corchetes. Una lista puede estar vacía, puede tener números y puede mezclar tipos:
vazia = []
notas = [8, 6, 7]
aluno = ["Ana", 20, 1.75, True]
print(vazia, notas, aluno)
print(list(range(5)))
print(list(range(2, 10, 3)))
print(list("abc"))
print([0] * 5)
print([1, 2] * 3)
print(len(vazia), len(notas), len(aluno))
print(type(notas))
print("fin")La salida tiene [] [8, 6, 7] ['Ana', 20, 1.75, True] en la primera línea, después [0, 1, 2, 3, 4], [2, 5, 8], ['a', 'b', 'c'], [0, 0, 0, 0, 0] y [1, 2, 1, 2, 1, 2]. La línea siguiente trae 0 3 4, los tamaños, y después vienen <class 'list'> y fin.
Ahí aparecen cuatro cosas. El [] crea la lista vacía, que es el punto de partida de casi todo programa que va a armar una lista poco a poco. El list(range(...)) transforma una secuencia del range, que viste en los bucles, en una lista de verdad. El list("abc") separa un texto en letras. Y el * repite el contenido, lo que da una lista de ceros lista con [0] * 5. La función len devuelve cuántos elementos tiene la lista.
La mezcla de tipos, como en el caso de aluno, está permitida, pero en la práctica una lista guarda elementos del mismo tipo y del mismo significado: todas las notas, todas las lecturas, todos los nombres. Cuando los elementos tienen papeles diferentes, como nombre, edad y altura, lo que se quiere es otra estructura, y los próximos artículos muestran cuáles.
Acceder a un elemento: el índice
Cada elemento tiene una posición, llamada índice, y empieza en 0. El primer elemento está en el índice 0, el segundo en el 1, y así sucesivamente. Esto confunde a quien está empezando, porque en la vida el primero es el número 1, pero es la regla de todo el lenguaje y no tiene excepción.
Los índices negativos cuentan desde el final: -1 es el último elemento, -2 es el penúltimo. Es la forma corta de pedir el último sin necesidad de calcular el tamaño.
valores = [12, 45, 7, 89, 23]
print(valores[0], valores[1], valores[4])
print(valores[-1], valores[-2], valores[-5])
print(valores[len(valores) - 1])
print(valores[1:4])
print(valores[:2])
print(valores[3:])
print(valores[::2])
print(valores[::-1])
print(valores[2:100])
print(valores[3:1])
print(valores)
print("fin")La salida es 12 45 23, 23 89 12 y 23, que muestran los índices positivos, los negativos y el cálculo manual del último. Las líneas de rebanada dan [45, 7, 89], [12, 45], [89, 23], [12, 7, 23], [23, 89, 7, 45, 12], [7, 89, 23] y [], y al final la lista original aparece intacta, seguida de fin.
Rebanadas: un pedazo de la lista
La rebanada usa dos puntos dentro de los corchetes, lista[inicio:fim], y entrega una lista nueva con los elementos desde el inicio hasta el fim, sin incluir el fim. Por eso valores[1:4] trae los índices 1, 2 y 3, y no el 4. Quien ya usó el range(1, 4) reconoce la regla, que es la misma.
Cuando se omite el inicio, la rebanada empieza desde el comienzo, y cuando se omite el fin, va hasta el final. El tercer número, opcional, es el paso: [::2] toma de dos en dos y [::-1] anda de atrás hacia adelante, lo que da la lista invertida.
Dos características de las rebanadas ahorran errores. No se quejan de límites exagerados: valores[2:100] simplemente va hasta el final, y valores[3:1], que pide un rango imposible, devuelve una lista vacía. Y nunca alteran la original. Es diferente de lo que ocurre con un solo índice, que fuera de la lista da error, como veremos más adelante.
La IA que recuerda tu vida y vive en tu ordenador
- Memoria a largo plazo con la fuente de cada respuesta
- Funciona sin conexión: tus datos se quedan en tu PC
- Lee PDF, diapositivas, fotos y grabaciones de reuniones
Plan Esencial gratis para siempre
Preguntar sobre la lista
Algunas preguntas sobre una lista son tan comunes que existen ya hechas:
dados = [12, 45, 7, 89]
print(45 in dados, 50 in dados, 50 not in dados)
print(dados.index(7))
print(dados.count(45))
print(len(dados), min(dados), max(dados), sum(dados))El in dice si el valor está en la lista, y el not in dice lo contrario. El index devuelve la posición de la primera aparición, el count cuenta cuántas veces aparece el valor, y len, min, max y sum dan el tamaño, el menor, el mayor y la suma. La salida es True False True, después 2, después 1 y, por último, 4 7 89 153.
Las funciones min, max y sum son atajos. Los desafíos de la plataforma piden las versiones hechas a mano, con bucle, para que el razonamiento quede entrenado, y esto se retomará en el próximo artículo.
Modificar elementos
Una lista puede cambiar después de creada. Quien llega del texto, que no acepta cambiar una letra, suele encontrarlo extraño, pero es el punto central de la estructura. Asignar a un índice cambia el elemento de esa posición:
lista = [10, 20, 30]
lista[1] = 25
print(lista)
lista[-1] = 99
print(lista)
lista[0] = lista[0] + 1
print(lista)
lista[0:2] = [1, 2, 3]
print(lista)La salida es [10, 25, 30], [10, 25, 99], [11, 25, 99] y [1, 2, 3, 99]. La tercera línea muestra el patrón de leer el valor, tocarlo y guardarlo de vuelta, que es el mismo del acumulador. La última asigna una lista a una rebanada: los dos elementos de las posiciones 0 y 1 fueron cambiados por tres, y la lista creció.
Para agregar o quitar elementos, la lista tiene métodos. Cada uno hace una sola cosa, y la imagen siguiente reúne los siete más usados, todos partiendo de la misma lista [10, 20, 30]:
En el programa, sin embargo, los comandos ocurren en secuencia, y cada uno parte del resultado del anterior:
lista = [10, 20, 30]
lista.append(40)
print(lista)
lista.insert(1, 15)
print(lista)
ultimo = lista.pop()
print(ultimo, lista)
primeiro = lista.pop(0)
print(primeiro, lista)
lista.remove(20)
print(lista)
lista.extend([40, 50])
print(lista)
del lista[1]
print(lista)
lista.clear()
print(lista)La salida es [10, 20, 30, 40], [10, 15, 20, 30, 40], 40 [10, 15, 20, 30], 10 [15, 20, 30], [15, 30], [15, 30, 40, 50], [15, 40, 50] y [].
El append pone un elemento al final y es el método más usado de la lista. El insert(posición, valor) pone en un lugar específico y empuja el resto hacia adelante. El pop() saca el último elemento y lo devuelve, y el pop(0) saca el primero. El remove(valor) busca el valor y borra la primera aparición, y el del lista[i] borra la posición i. El extend agrega varios elementos de una vez, y el clear vacía la lista.
Hay una diferencia entre estos comandos que importa más de lo que parece: el pop devuelve el elemento quitado, y todos los otros devuelven None. El motivo es que ya alteraron la lista y no tienen nada que entregar. La consecuencia aparece en la sección de errores.
append, extend y +: la diferencia que engaña
Tres maneras de juntar cosas a la lista dan resultados diferentes, y con prisa se confunden:
a = [1, 2]
a.append([3, 4])
print(a, len(a))
b = [1, 2]
b.extend([3, 4])
print(b, len(b))
print([1, 2] + [3, 4])
d = [1, 2, 3, 2, 2]
d.remove(2)
print(d, d.index(2))La salida es [1, 2, [3, 4]] 3, [1, 2, 3, 4] 4, [1, 2, 3, 4] y [1, 3, 2, 2] 2. El append pone lo que reciba como un solo elemento, y por eso la lista [3, 4] entró entera y la lista quedó con tres elementos, uno de ellos siendo otra lista. El extend abre lo que recibió y pone los elementos uno a uno. El + junta dos listas en una tercera, sin alterar las originales, y por eso se usa en una expresión, no como comando suelto.
La última parte del ejemplo muestra que el remove(2) borró solo el primer 2. Los otros dos siguieron en la lista, y el index(2) ahora encuentra uno de ellos en la posición 2. Para borrar todas las apariciones hace falta un bucle o la comprensión de lista, que el próximo artículo presenta.
Una lista, dos nombres
Este es el punto que más sorprende a quien viene de variables con números y textos. Con un número, b = a copia el valor, y tocar b no cambia a. Con una lista no es así. El b = a no crea una lista nueva: hace que el nombre b apunte a la misma lista que a. Los dos nombres son dos etiquetas en la misma caja.
a = [1, 2, 3]
b = a
b.append(4)
print(a, b, a is b)
c = a.copy()
c.append(5)
print(a, c, a is c)
d = a[:]
e = list(a)
print(d is a, e is a, d == a)La salida es [1, 2, 3, 4] [1, 2, 3, 4] True, después [1, 2, 3, 4] [1, 2, 3, 4, 5] False y, por último, False False True. El operador is pregunta si los dos nombres apuntan al mismo objeto, y el == pregunta si el contenido es igual. Después de b = a, el is da True: solo existe una lista. Después de copy(), da False: existen dos, y tocar una no afecta a la otra. Las formas a[:] y list(a) también hacen copia, y quedan a elección de quien escribe. La pregunta sobre el contenido, d == a, sigue siendo verdadera, porque la copia tiene los mismos valores.
Esta característica tiene el nombre de mutabilidad: la lista puede alterarse en el lugar, y por eso quien tiene su nombre ve la alteración. Números, textos y tuplas son inmutables, y por eso no tienen esta trampa.
Qué cambia esto en las funciones
El mismo principio vale cuando la lista se pasa a una función, como se vio en el artículo sobre parámetros y argumentos. El parámetro es un nuevo nombre para la lista de quien llama, y no una copia:
def adicionar(lista, x):
lista.append(x)
def reatribuir(lista):
lista = [0]
dados = [1, 2]
adicionar(dados, 3)
print(dados)
reatribuir(dados)
print(dados)Las dos líneas de la salida son [1, 2, 3]. En la primera, el append alteró la lista que quien llama también ve, y por eso el cambio sobrevivió a la función. En la segunda, lista = [0] solo hizo que el nombre local apuntara a otra lista, y la de afuera quedó como estaba. Es la misma lógica de los nombres locales del artículo sobre alcance de variables: tocar el contenido es una cosa, cambiar a qué apunta el nombre es otra.
Recorrer una lista
Recorrer es pasar por todos los elementos, y el lenguaje ofrece más de una manera. Cada una sirve a una necesidad.
notas = [8, 6, 7]
for n in notas:
print(n)
for i in range(len(notas)):
print(i, notas[i])
for i, n in enumerate(notas):
print(i, n)
for i, n in enumerate(notas, 1):
print("alumno", i, n)El primer bucle, for n in notas, entrega cada elemento, y es el que se usa cuando la posición no importa. El segundo recorre los índices con range(len(notas)) y lee el elemento por notas[i], que sirve cuando la posición es necesaria, pero da más trabajo. El enumerate entrega la posición y el elemento juntos, y es la forma más limpia de tener los dos. El segundo argumento, enumerate(notas, 1), cambia dónde empieza la cuenta, lo que ayuda cuando la posición se mostrará a una persona que cuenta a partir de 1.
Tres variantes completan el conjunto:
notas = [8, 6, 7]
i = 0
while i < len(notas):
print(notas[i], end=" ")
i += 1
print()
for n in reversed(notas):
print(n, end=" ")
print()
for num, letra in zip([1, 2, 3], ["a", "b", "c"]):
print(num, letra)La primera línea de la salida es 8 6 7, escrita por el while con índice, que solo vale la pena cuando el paso no es de uno en uno o cuando la lista cambia durante el bucle. La segunda es 7 6 8, por el reversed, que anda de atrás hacia adelante sin crear una lista nueva. Después vienen 1 a, 2 b y 3 c, donde el zip recorre dos listas al mismo tiempo, emparejando los elementos de la misma posición.
No quites elementos mientras recorres
Este es el error que no da ningún mensaje. Quien recorre una lista y borra elementos de ella al mismo tiempo acaba con un resultado equivocado, sin aviso:
nums = [2, 4, 6, 8]
for n in nums:
nums.remove(n)
print(nums)
nums = [2, 4, 6, 8]
for n in nums[:]:
nums.remove(n)
print(nums)La salida es [4, 8] y después []. En la primera versión la intención era vaciar la lista, y sobraron dos elementos. El bucle anda por posición: borra el 2, los elementos se deslizan a la izquierda, y el siguiente paso mira la posición 1, que ahora guarda el 6, y el 4 fue saltado. La segunda versión recorre una copia, nums[:], y borra en la original, y por eso da el resultado esperado. El camino más claro casi siempre es armar una lista nueva solo con lo que debe quedar. Por ejemplo, [n for n in [2, 4, 6, 8, 9] if n % 2 != 0] da [9]. Esa forma, la comprensión de lista, es el tema del próximo artículo.
Acumuladores sobre una lista
Los patrones de los bucles, como sumar, hallar el mayor y contar, aparecen con listas todo el tiempo. La estructura es la misma: una variable antes del bucle, que se actualiza con cada elemento.
valores = [12, 45, 7, 89, 23]
soma = 0
maior = valores[0]
pares = 0
for v in valores:
soma += v
if v > maior:
maior = v
if v % 2 == 0:
pares += 1
print("suma", soma)
print("mayor", maior)
print("pares", pares)
print("media", soma / len(valores))La salida es suma 176, mayor 89, pares 1 y media 35.2. El maior empieza con el primer elemento de la lista, y no con 0, porque si todos los valores fueran negativos el 0 ganaría sin estar en la lista. El patrón de partir del primer elemento solo vale con la lista no vacía, y una lista vacía daría error de índice ya en la primera línea. Un programa que recibe listas de afuera necesita comprobar el len antes.
Listas en la ESP32
En la ESP32, la lista guarda lecturas y guarda objetos. El primer caso es la ventana móvil, que mantiene solo las últimas lecturas de un sensor para sacar un promedio y suavizar la oscilación:
leituras = (510, 520, 505, 540, 530)
janela = []
for x in leituras:
janela.append(x)
if len(janela) > 3:
janela.pop(0)
print(janela, sum(janela) // len(janela))La salida es [510] 510, [510, 520] 515, [510, 520, 505] 511, [520, 505, 540] 521 y [505, 540, 530] 525. Con cada lectura nueva, el append entra al final, y cuando la ventana pasa de tres elementos el pop(0) saca la más antigua. La cuenta usa // para el promedio entero. En una placa de verdad, leituras vendría del adc.read() dentro de un bucle con sleep; el artículo sobre ADC en la ESP32 muestra la lectura, y el tamaño de la ventana es una elección entre respuesta rápida y lectura estable.
El segundo caso es guardar los propios pines. En lugar de tres variables led1, led2 y led3, una lista:
from machine import Pin
from time import sleep
leds = [Pin(18, Pin.OUT), Pin(19, Pin.OUT), Pin(21, Pin.OUT)]
for led in leds:
led.on()
sleep(0.1)
led.off()
print("leds:", len(leds))
print("fin")La salida es leds: 3 y fin, y el bucle enciende y apaga cada LED en el orden de la lista. Para agregar un cuarto LED, basta poner otro Pin en la lista, y el bucle no cambia. Si el programa necesita el número de la posición, el bucle puede usar range(len(leds)) y escribir leds[i], como en el desafío Semáforo, que pide tres LED y en que el orden de la secuencia es el propio problema.
Una salvedad sobre la memoria: la ESP32 tiene poca RAM, y una lista que crece sin límite, por ejemplo guardando una lectura por segundo todo el día, acaba agotando la memoria. La ventana móvil del ejemplo anterior lo evita por construcción, porque la lista nunca pasa de tres elementos.
Errores comunes
Seis mensajes cubren casi todo lo que sale mal con listas. Los textos de abajo son los del simulador, que imita a MicroPython:
lista = [10, 20, 30]
print(lista[3])IndexError: list index out of rangeEl índice 3 no existe en una lista de tres elementos, porque los índices van de 0 a 2. Es el error más frecuente, y la causa casi siempre es el lista[len(lista)], que intenta leer una posición después de la última.
lista = []
lista.pop()IndexError: pop from empty listEl pop necesita al menos un elemento. Cuando la lista puede estar vacía, el if lista: antes del pop evita el error.
lista = [1, 2, 3]
lista.remove(9)ValueError: object not in sequenceEl remove y el index dan ValueError cuando el valor no está en la lista. El mensaje del Python de computador es otro, pero el tipo del error es el mismo. La defensa es preguntar antes con if 9 in lista:.
lista = [1, 2, 3]
print(lista[1.5])TypeError: list indices must be integers, not floatEl índice tiene que ser un entero, y un float se rechaza. Aparece cuando el índice viene de una división con /, que siempre devuelve float, y la corrección es usar //. Con un texto en su lugar, el mensaje es el mismo, con str al final.
lista = [1, 2, 3]
print(lista("0"))TypeError: 'list' object isn't callableLos paréntesis llaman funciones y los corchetes acceden a posiciones. Escribir lista(0) intenta llamar a la lista como si fuera una función.
lista = [1, 2, 3]
lista = lista.append(4)
print(lista)
lista.append(5)None
AttributeError: 'NoneType' object has no attribute 'append'Es el efecto de lo que la sección de métodos anunció: el append devuelve None, y la asignación lista = lista.append(4) cambia la lista por None. El print muestra None, y el próximo append falla, porque None no tiene ese método. El append altera la lista en el lugar y no necesita asignación.
Lo que suele aparecer en el aula
Los patrones de abajo se repiten en casi todo grupo que llega a las listas, desde principiantes hasta gente con años de programación.
El primero es el índice. Quien cuenta desde 1 acaba leyendo una posición de más, y el lista[len(lista)] da IndexError siempre. La cura es memorizar una frase: en una lista de n elementos, los índices van de 0 a n - 1, y el último también puede leerse con -1.
El segundo es el lista = lista.append(x), visto arriba. Casi todos los que escriben esto ya hicieron la misma asignación con textos y números, en que es necesaria, y llevan la misma idea a un método que altera en el lugar.
El tercero es el b = a creyendo que es copia. El síntoma es una lista que cambia "sola" en otro punto del programa, y la investigación suele terminar en una asignación olvidada quince líneas más allá. Una pista útil de depuración es imprimir a is b.
El cuarto es borrar elementos dentro del for, que no da error y entrega un resultado equivocado, lo que vuelve el problema más difícil de encontrar que un error con mensaje.
El quinto es confundir append con extend, y la lista con una lista dentro, que solo aparece cuando el programa intenta sumar o imprimir los elementos. El sexto es esperar que el remove borre todas las apariciones. Y el séptimo, conocido por quien leyó el artículo sobre parámetros, es usar [] como valor por defecto de un parámetro, lo que hace que la lista se comparta entre todas las llamadas de la función.
Ejercicio: informe de notas
Escribe un programa que pregunte cuántas notas se leerán, lea cada una, las guarde en una lista y muestre la lista, el promedio, la mayor nota y las notas por encima del promedio.
quantidade = int(input("cuántas notas: "))
notas = []
for i in range(quantidade):
n = int(input("nota: "))
notas.append(n)
media = sum(notas) / len(notas)
print("notas", notas)
print("media", media)
print("mayor", max(notas))
acima = []
for n in notas:
if n > media:
acima.append(n)
print("por encima de la media", acima)Con las entradas 3, 8, 6 y 7, las cuatro líneas finales son notas [8, 6, 7], media 7.0, mayor 8 y por encima de la media [8]. La lista nace vacía y crece con el append, el promedio usa sum y len, y la lista acima se arma con el mismo patrón. Para entrenar, cambia el max por un bucle con maior y haz que el programa muestre también las notas por debajo del promedio. ¿Qué ocurre si el usuario escribe 0 en la primera pregunta? Pruébalo y protege el programa.
Preguntas frecuentes
¿Cuál es la diferencia entre lista y tupla? La lista puede alterarse después de creada y la tupla no. La tupla ya apareció al devolver varios valores de una función, y el artículo 24 explica cuándo preferirla.
¿Por qué los desafíos hablan de "vector"? Es el nombre que se usa en lógica de programación para una secuencia de elementos del mismo tipo. En Python, el vector es una lista. Los desafíos l49 a l54 tienen esa palabra en el nombre, pero el código que se escribe en ellos es una lista común.
¿Una lista puede tener elementos de tipos diferentes? Puede, pero solo vale la pena cuando los elementos significan cosas diferentes y la posición de cada uno es fija. Para listas de cosas del mismo tipo, la mezcla suele ser señal de error.
¿Puedo tener una lista dentro de otra? Puedes, y así se representa una tabla, con filas y columnas. Esto es tema del artículo 28, sobre matrices.
¿Cuál es la diferencia entre pop, remove y del? El pop actúa por la posición y devuelve el elemento. El remove actúa por el valor. El del actúa por la posición y no devuelve nada.
Para continuar
Los desafíos l49-vetor-primeiro, l50-vetor-len, l51-vetor-soma, l52-vetor-busca, l53-vetor-inversao y l54-vetor-medio practican exactamente lo que se vio aquí, desde el acceso por índice hasta el promedio de una lista.
Para repasar las bases de esta lectura, los artículos sobre funciones con def, return y lambda, map, filter y sorted muestran cómo las funciones reciben y devuelven listas.
El próximo artículo trata de las operaciones con listas: búsqueda, filtrado y comprensiones de lista. Muestra cómo hallar un valor sin index, cómo armar una lista nueva solo con lo que interesa y cómo escribir en una línea lo que aquí llevó cuatro.
Para practicar en PyPlaca
Rutas
Lecciones
- Primero de la lista
- Tamaño de la lista
- Suma de la lista
- Búsqueda en la lista
- Lista al revés
- Media de la lista