Python
Listas: la colección todoterreno de Python
Cómo crear listas, acceder por índice, cortarlas con slicing, modificarlas y por qué copiarlas con b = a no copia nada.
De dónde venimos
Hasta ahora, cada variable que creaste guardaba un solo valor: un nombre, una edad, una nota. En Funciones apareció por primera vez algo distinto —notas = [6.5, 3.0, 4.5, 8.0]— y en Control de flujo ya recorriste una lista con un for, pero en ambos casos se usó lo mínimo indispensable y quedó prometido volver.
Esta página cumple esa promesa. Si tu programa necesita guardar las notas de un curso, los nombres de los asistentes o las últimas veinte mediciones de un sensor, crear veinte variables (nota1, nota2, nota3…) es inviable: no puedes recorrerlas con un bucle ni pasarlas juntas a una función. Una lista resuelve exactamente eso: es un solo valor que contiene muchos valores ordenados.
Qué es una lista
Una lista es una secuencia ordenada de valores, escrita entre corchetes [] y separada por comas:
numeros = [1, 2, 3]
nombres = ["Ana", "Luis", "Carla"]
vacia = []
print(numeros)
print(nombres)
print(vacia)
print(len(numeros))Guárdalo como listas.py y ejecútalo:
python listas.py[1, 2, 3]
['Ana', 'Luis', 'Carla']
[]
3Fíjate en dos detalles de la salida: la lista vacía se imprime como [], y las cadenas dentro de una lista aparecen entre comillas simples aunque tú las hayas escrito con comillas dobles. Es solo cómo Python decide mostrarlas; el valor es el mismo.
len() —la misma función que ya usaste sobre cadenas de texto— devuelve cuántos elementos tiene la lista.
Listas de tipos mixtos
Una lista no obliga a que todos sus elementos sean del mismo tipo. Esto es perfectamente válido:
mezcla = ["Ana", 30, 1.75, True]
for elemento in mezcla:
print(elemento, type(elemento))Ana <class 'str'>
30 <class 'int'>
1.75 <class 'float'>
True <class 'bool'>Si vienes de C++ o Java, donde un array tiene un tipo fijo declarado, esto sorprende. Es la consecuencia directa del tipado dinámico: la lista no guarda “cuatro enteros”, guarda “cuatro valores, cada uno con su propio tipo”.
Que se pueda no significa que convenga. En la práctica, una lista casi siempre guarda cosas del mismo tipo (todas notas, todos nombres), porque así puedes recorrerla y aplicarle la misma operación a cada elemento sin comprobar qué es cada uno. Las listas mixtas suelen ser señal de que lo que necesitas es un diccionario o una clase, dos cosas que verás en las páginas siguientes.
Acceder por posición: el índice
Cada elemento tiene una posición (su índice), y se accede a él escribiendo esa posición entre corchetes después del nombre de la lista.
Los índices empiezan en 0, no en 1. El primer elemento es lista[0], el segundo es lista[1], y el último de una lista de 4 elementos es lista[3]. Es la fuente número uno de errores “por uno” al empezar.
numeros = [10, 20, 30, 40]
print(numeros[0]) # el primero
print(numeros[3]) # el cuarto y último
print(len(numeros))10
40
4Pedir un índice que no existe detiene el programa con IndexError: list index out of range. En una lista de 4 elementos, numeros[4] ya se pasa: el último índice válido siempre es len(lista) - 1. Este error es especialmente fácil de provocar dentro de un for con range(), si el límite superior se calcula mal.
Índices negativos
Python admite además índices negativos, que cuentan desde el final: -1 es el último elemento, -2 el penúltimo, y así hacia atrás.
numeros = [10, 20, 30, 40]
print(numeros[-1]) # último
print(numeros[-2]) # penúltimo
print(numeros[len(numeros) - 1]) # el mismo último, calculado a mano40
30
40Las dos últimas líneas dan lo mismo, y esa es justamente la razón de que los índices negativos existan: acceder al final de una secuencia es una operación tan común que tener que escribir lista[len(lista) - 1] cada vez es ruidoso y fácil de equivocar (basta olvidar el - 1 para provocar un IndexError). lista[-1] dice “el último” sin cuentas de por medio.
Slicing: cortar trozos con dos puntos
Además de un elemento suelto, puedes pedir un trozo de la lista (en inglés, slice). La sintaxis va dentro de los mismos corchetes, pero con dos puntos separando las partes:
lista[inicio:fin:paso]inicio— índice donde empieza el corte. Se incluye. Si lo omites, se asume el principio de la lista.fin— índice donde termina el corte. No se incluye. Si lo omites, se asume el final de la lista.paso— de cuánto en cuánto avanzar. Si lo omites, se asume 1 (uno por uno). Puede ser negativo para recorrer hacia atrás.
Los tres son opcionales, y de ahí salen todas las formas que verás en código real:
numeros = [10, 20, 30, 40, 50, 60]
print(numeros[1:3]) # del índice 1 al 2 (el 3 no entra)
print(numeros[:2]) # desde el principio hasta el índice 1
print(numeros[3:]) # desde el índice 3 hasta el final
print(numeros[-2:]) # los dos últimos
print(numeros[:]) # una copia entera
print(numeros[::2]) # de dos en dos
print(numeros[1::2]) # de dos en dos, empezando en el índice 1
print(numeros[::-1]) # la lista al revésGuárdalo como slicing.py y ejecútalo:
python slicing.py[20, 30]
[10, 20]
[40, 50, 60]
[50, 60]
[10, 20, 30, 40, 50, 60]
[10, 30, 50]
[20, 40, 60]
[60, 50, 40, 30, 20, 10]Cuatro observaciones sobre esa salida:
- Un slice siempre devuelve una lista nueva, aunque tenga un solo elemento o ninguno.
numeros[1:3]es[20, 30], una lista;numeros[1]es20, un número. No es lo mismo. - El límite superior nunca se incluye, igual que en
range(). Por esonumeros[1:3]devuelve dos elementos y no tres: la cuenta esfin - inicio. [::-1]es el modismo estándar para invertir una secuencia: paso-1significa “recórrela de atrás hacia adelante”.- Un slice fuera de rango no falla.
numeros[2:100]devuelve simplemente lo que haya desde el índice 2 en adelante, sin lanzarIndexError. Es la diferencia de comportamiento más útil (y más olvidada) entre indexar y cortar.
El slicing también funciona sobre cadenas de texto, porque una cadena es igualmente una secuencia:
palabra = "Python"
print(palabra[0]) # un carácter suelto
print(palabra[0:3]) # los tres primeros
print(palabra[-3:]) # los tres últimos
print(palabra[::-1]) # al revésP
Pyt
hon
nohtyPEstos dos puntos no son los de un bloque
Aquí conviene detenerse, porque el carácter : ya apareció antes en esta documentación con un significado completamente distinto.
- En ¿Qué es Python? y en La indentación, ahora en la práctica viste que
:cierra la cabecera de un bloque (if,for,while,def) y anuncia que debajo viene el cuerpo indentado. - Aquí, dentro de
[], ese mismo carácter separa las tres partes de un slice, y no abre ningún bloque ni tiene nada que ver con la indentación.
numeros = [10, 20, 30, 40]
for numero in numeros[1:3]: # el primer ":" es slicing, el último abre el bloque
print(numero)20
30En Python el mismo símbolo puede significar cosas distintas según dónde aparece. Lo que determina el significado de : es su contexto: dentro de corchetes es un slice; al final de una cabecera de bloque es el inicio de un bloque indentado. No hay ambigüedad para el intérprete, pero sí para quien lee rápido. Este tema se retoma ordenado, con todos los símbolos juntos, en Repaso de sintaxis.
Las listas se pueden modificar
Una lista es mutable: puedes cambiar su contenido después de haberla creado, sin necesidad de crear una nueva. Se le asigna a una posición como si fuera una variable:
numeros = [10, 20, 30]
numeros[0] = 99
print(numeros)[99, 20, 30]Esto es una diferencia real con las cadenas de texto, que son inmutables: no se pueden modificar por dentro. Cualquier operación que “cambie” una cadena en realidad construye una cadena nueva.
texto = "hola"
print(texto[0]) # leer sí se puede
texto = texto + "!" # esto no modifica: crea una cadena nueva y la reasigna
print(texto)
# texto[0] = "H" # esto falla: TypeErrorh
hola!Si descomentas la última línea, el programa se detiene con TypeError: 'str' object does not support item assignment. La regla práctica: en una lista puedes escribir por índice; en una cadena, solo leer.
Métodos para agregar, quitar y ordenar
Un método es una función que se llama “sobre” un valor, escribiendo un punto entre el valor y el nombre: numeros.append(4). Ya usaste esta forma sin nombrarla. Estos son los métodos de lista que necesitas desde el primer día:
| Método | Qué hace |
|---|---|
.append(x) | Agrega x al final |
.insert(i, x) | Inserta x en la posición i, corriendo el resto |
.remove(x) | Elimina la primera aparición del valor x |
.pop() | Saca y devuelve el último elemento |
.pop(i) | Saca y devuelve el elemento de la posición i |
.sort() | Ordena la lista en su sitio |
.reverse() | Invierte la lista en su sitio |
numeros = [10, 20, 30]
numeros.append(40)
print(numeros)
numeros.insert(1, 15)
print(numeros)
numeros.remove(20)
print(numeros)
ultimo = numeros.pop()
print(ultimo, numeros)
numeros.sort()
print(numeros)Guárdalo como metodos.py y ejecútalo:
python metodos.py[10, 20, 30, 40]
[10, 15, 20, 30, 40]
[10, 15, 30, 40]
40 [10, 15, 30]
[10, 15, 30].sort() ordena la lista y devuelve None, igual que cualquier función sin return explícito (lo viste en Devolver un resultado con return). Por eso numeros = numeros.sort() es un error clásico y destructivo: ordena la lista y acto seguido reemplaza la variable por None, perdiéndola entera. Se escribe numeros.sort() a secas. Si lo que quieres es una copia ordenada sin tocar la original, usa la función sorted(numeros), que sí devuelve una lista nueva.
.remove(x) recibe el valor a eliminar, no su posición; .pop(i) recibe la posición. Confundirlos no siempre da error: en una lista de números como [3, 1, 0], .remove(0) elimina el valor 0 (el último elemento) mientras que .pop(0) elimina el primero. Dos resultados distintos con el mismo argumento.
Recorrer una lista
Recorrer una lista es exactamente el for que ya conoces: en cada vuelta, la variable del bucle toma el siguiente elemento. No hay nada nuevo que aprender aquí, solo dos formas que conviene tener a mano:
nombres = ["Ana", "Luis", "Carla"]
for nombre in nombres:
print("Hola,", nombre)
for i, nombre in enumerate(nombres):
print(i, "->", nombre)Hola, Ana
Hola, Luis
Hola, Carla
0 -> Ana
1 -> Luis
2 -> Carlaenumerate() es la forma idiomática de recorrer una lista cuando además necesitas la posición de cada elemento: entrega el índice y el elemento a la vez, y evita el for i in range(len(nombres)) que se desaconsejaba en el for de la página de control de flujo.
Copiar una lista sin copiarla
Este es, con diferencia, el error que más sorprende a quien empieza con listas.
a = [1, 2, 3]
b = a # esto NO es una copia
b.append(4)
print("a:", a)
print("b:", b)
print(a is b)Guárdalo como copia.py y ejecútalo:
python copia.pya: [1, 2, 3, 4]
b: [1, 2, 3, 4]
TrueModificaste b y cambió a. No es un bug: b = a no copia la lista, copia solo la referencia. Recuerda que una variable en Python es un nombre que apunta a un valor; tras b = a hay una sola lista en memoria y dos nombres apuntando a ella. Tocarla por cualquiera de los dos nombres la cambia para ambos. El operador is (distinto de ==) pregunta justamente eso: “¿son el mismo objeto?”, y responde True.
Con valores inmutables esto nunca se nota. Si escribes x = 5; y = x; y = 6, la variable x sigue valiendo 5, porque y = 6 no modifica el número: apunta y a otro valor. Las listas, al ser mutables, sí se pueden modificar por dentro, y ahí es donde el detalle se vuelve visible.
Para copiar de verdad una lista usa a.copy() o el slice completo a[:]. Ambas crean una lista nueva con los mismos elementos. Este error es especialmente traicionero cuando la lista se pasa a una función: si el cuerpo de la función hace .append() sobre el parámetro, está modificando la lista del que llamó, no una copia local.
a = [1, 2, 3]
b = a.copy()
c = a[:]
b.append(4)
c.append(5)
print("a:", a)
print("b:", b)
print("c:", c)
print(a is b)a: [1, 2, 3]
b: [1, 2, 3, 4]
c: [1, 2, 3, 5]
FalseAhora sí: a quedó intacta y cada copia siguió su propio camino.
Ejemplo completo: promedio de una cantidad desconocida de números
Este programa junta todo lo de la página con lo de las anteriores: un while True con break para repetir hasta que el usuario decida parar, input() con conversión de tipo, una lista que crece con .append(), y un for que acumula el total.
numeros = []
while True:
entrada = input("Ingresa un número (o 'fin' para terminar): ")
if entrada == "fin":
break
numeros.append(float(entrada))
if len(numeros) == 0:
print("No ingresaste ningún número.")
else:
total = 0
for numero in numeros:
total += numero
print("Ingresaste", len(numeros), "números:", numeros)
print("El mayor es:", max(numeros))
print("El menor es:", min(numeros))
print("El promedio es:", total / len(numeros))Guárdalo como promedio_lista.py y ejecútalo:
python promedio_lista.pyIngresa un número (o 'fin' para terminar): 5
Ingresa un número (o 'fin' para terminar): 3
Ingresa un número (o 'fin' para terminar): 7
Ingresa un número (o 'fin' para terminar): fin
Ingresaste 3 números: [5.0, 3.0, 7.0]
El mayor es: 7.0
El menor es: 3.0
El promedio es: 5.0Lo que hace funcionar este programa:
- La lista empieza vacía y crece una posición por cada número válido. No hay que decidir de antemano cuántos números va a ingresar el usuario, algo imposible con variables sueltas.
- Los valores se guardan como
floatporqueinput()siempre devuelve texto. Por eso la lista se imprime como[5.0, 3.0, 7.0]y no como[5, 3, 7]. - El
if len(numeros) == 0no es paranoia: sin él, si el usuario escribefinde inmediato, la divisióntotal / len(numeros)sería una división por cero y el programa se caería conZeroDivisionError. max()ymin()son funciones incluidas en Python que devuelven el mayor y el menor elemento de una secuencia.
El bucle que acumula en total se puede reemplazar por la función sum(numeros), que hace exactamente eso mismo en una sola llamada. Se escribió a mano aquí para que veas el mecanismo, pero en código real se usa sum(numeros) / len(numeros). Lo mismo vale para max() y min(): aprender qué funciones ya vienen hechas ahorra la mitad del código.
Si el usuario escribe algo que no es un número ni la palabra fin (por ejemplo hola), float() lanza ValueError y el programa se detiene. Es la misma limitación que ya se señaló al presentar input(): validar entradas con elegancia requiere capturar errores, un tema posterior.
Siguiente paso
Las listas cubren el caso más común, pero no todos. A veces necesitas una colección que no se pueda modificar por accidente, o buscar un dato por un nombre en vez de por una posición numérica, o garantizar que no haya elementos repetidos. Para eso están las otras tres colecciones del lenguaje: continúa con Tuplas, diccionarios y conjuntos.