Skip to content
This page has been auto-translated and may contain errors.View in English

Listas

docs.scrimba.com

Una variable guarda una cosa. Una lista guarda muchas cosas en orden, todas bajo un mismo nombre. Una tabla de clasificación es una secuencia ordenada de puntuaciones. Un cuestionario es una colección de preguntas. Cuando necesitas manejar un grupo de valores relacionados, necesitas una lista.

Las listas son la secuencia ordenada y mutable de propósito general de Python. Son la opción natural para cualquier cosa que cambia con el tiempo: elementos añadidos o removidos, orden reorganizado, contenido filtrado u ordenado. Cuando el orden importa y la colección cambia, una lista suele ser la primera opción correcta.

Una list es el arreglo dinámico de Python: una secuencia ordenada y mutable (modificable en el lugar). El perfil de desempeño es lo que impulsa las decisiones de diseño. La lectura por índice es O(1), tiempo constante sin importar cuán grande sea la lista (big-O es una abreviatura de cómo el costo escala con el tamaño, n). append() es efectivamente O(1) también: la lista mantiene espacio de sobra al final y solo se redimensiona ocasionalmente. Los costos que duelen son insert() y remove() en O(n), porque cada elemento después del punto de cambio se desplaza. Cuando te encuentras insertando o removiendo al frente en un bucle, ese O(n) es la señal de que debes usar una estructura diferente.

Crear una lista

Corchetes cuadrados, valores separados por comas. Las listas pueden contener cualquier mezcla de tipos, y una lista vacía es válida y común como punto de partida que construyes con el tiempo.

Las listas se definen con sintaxis de corchetes y preservan el orden de inserción. Pueden contener cualquier valor de Python, incluidas otras listas. La lista vacía [] es el punto de partida estándar cuando acumulas elementos de manera incremental.

Un literal de corchete crea un nuevo objeto list, y la lista almacena referencias a sus elementos, no los valores copiados (una referencia es un puntero a dónde vive el objeto real). Entonces un elemento puede ser cualquier objeto de Python, incluida otra lista. Mezclar tipos en una lista es válido pero poco común fuera de scripts rápidos: la mayoría de las listas que mantienes contienen un tipo, lo que las hace más fáciles de entender y procesar en un bucle.

python
scores = [87, 92, 74, 65, 91]
players = ["María", "Carlos", "Juan"]
mixed = ["María", 87, True, 3.14]   # cualquier tipo, aunque poco común
empty = []
JunoCrear una lista Valores entre corchetes cuadrados, separados por comas, y Python los mantiene en el orden que escribiste. Un [] vacío es un buen punto de partida cuando planeas llenarlo conforme avances. La mayoría de mis listas comienzan así, vacías y esperando.
JunoCrear una lista Los corchetes definen una lista y el orden de inserción se preserva. Una lista puede contener cualquier valor, incluidas otras listas, y [] es el inicio estándar cuando acumulas elementos de manera incremental.
JunoCrear una lista Una lista contiene referencias a sus elementos, por lo que puede mezclar cualquier objeto, aunque lo que realmente quieres mantener es un tipo por lista. Usa [] y ve construyendo, la sobreasignación lo hace barato.

Indexación y división

Las listas usan la misma numeración que las cadenas: las posiciones comienzan en 0, los números negativos cuentan desde el final. Puedes leer cualquier elemento por su posición. Porque las listas son mutables, también puedes escribir en una posición específica.

La indexación y división de listas siguen las mismas reglas que las cadenas. La diferencia clave es la mutabilidad: puedes asignar a un índice o a una división para cambiar elementos en el lugar, algo que las cadenas no permiten.

La indexación y división se leen igual que en una cadena, pero una lista te permite asignar también, ya que es mutable. Lo que debes saber es la asignación de división: puede cambiar la longitud de la lista. lst[1:3] = [10, 20, 30] reemplaza los dos elementos en las posiciones 1 y 2 con tres nuevos, haciendo crecer la lista por uno. El reemplazo no tiene que coincidir con la longitud de la división, lo cual es conveniente y una sorpresa común, así que úsalo deliberadamente en lugar de por accidente.

python
scores = [87, 92, 74, 65, 91]

scores[0]      # 87  (primero)
scores[-1]     # 91  (último)
scores[1:3]    # [92, 74]
scores[:2]     # [87, 92]
scores[::-1]   # [91, 65, 74, 92, 87]  (invertido)

scores[0] = 90   # mutable: funciona (las cadenas lanzarían TypeError)
JunoIndexación y división Las posiciones comienzan en 0, y los números negativos cuentan hacia atrás desde el final, entonces scores[-1] es el último. Una división como scores[1:3] te entrega una lista nueva con las posiciones 1 y 2. La parte que me tomó un momento: con una lista también puedes escribir en una posición, scores[0] = 90, donde una cadena se rehusamía.
JunoIndexación y división Igual indexación y división que las cadenas, posiciones desde 0, negativos desde el final. La diferencia es la mutabilidad: asigna a un índice o a una división y cambias la lista en el lugar, donde una cadena lanzaría TypeError.
JunoIndexación y división Se lee como la indexación de cadenas, pero puedes asignar, y la asignación de división puede cambiar el tamaño de la lista. lst[1:3] = [10, 20, 30] canjea dos elementos por tres, conveniente a propósito y una sorpresa por accidente, así que úsalo deliberadamente.

Añadir elementos

Tres métodos para añadir elementos. append() añade un único elemento al final y es lo que usarás casi siempre. insert() añade en una posición específica. extend() fusiona otra lista.

append() es O(1) amortizado (la lista mantiene espacio de sobra y solo se redimensiona ocasionalmente, por lo que el costo promedio se mantiene constante) y es la forma estándar de construir una lista elemento por elemento. insert() es O(n) porque desplaza elementos posteriores. extend() es equivalente a += y más eficiente que append() repetido dentro de un bucle.

append() es barato porque la lista mantiene espacio de sobra y solo se redimensiona de vez en cuando, por lo que el costo promedio se mantiene O(1) (constante, independientemente de la longitud). insert(0, x) es la trampa: insertar al frente es O(n) porque cada elemento se desplaza un espacio a la derecha. Si construyes una lista añadiendo repetidamente al frente, eso se convierte en O(n al cuadrado) en general. Cuando necesitas inserción rápida al frente, usa collections.deque de la biblioteca estándar, cuyo appendleft es O(1), cubierto en el capítulo Módulos. extend() hace crecer la lista en una pasada, por eso vence un bucle de llamadas append() cuando ya tienes los elementos.

python
scores = [87, 92, 74]

scores.append(65)          # [87, 92, 74, 65]
scores.insert(1, 100)      # [87, 100, 92, 74, 65]
scores.extend([55, 71])    # [87, 100, 92, 74, 65, 55, 71]

Un error común: append() con una lista añade toda la lista como un elemento, dándote una lista dentro de una lista. Usa extend() para fusionar en su lugar:

append(x) siempre añade x como un único elemento. Pasar una lista a append() te da una lista anidada. Usa extend() cuando quieres fusionar todos los elementos de otra lista en esta:

append(x) añade x como un elemento sin importar qué sea x, entonces entregarle una lista anida esa lista completa. extend(iterable) camina el argumento y añade cada elemento por separado. lst += other hace lo mismo que extend, así que elige el que lea más claro en el sitio de la llamada.

python
scores.append([55, 71])    # [..., [55, 71]]  lista anidada, probablemente incorrecto
scores.extend([55, 71])    # [..., 55, 71]    fusionado, correcto
JunoAñadir elementosappend() agrega un elemento al final y es lo que alcanzarás casi cada vez. insert() coloca un elemento en una posición, extend() fusiona otra lista. El error clásico: append() una lista y obtienes una lista dentro de tu lista, así que usa extend() cuando quieras fusionar.
JunoAñadir elementosappend() es tu predeterminado, elemento por elemento al final. insert() desplaza todo lo que viene después, entonces cuesta más. append() una lista y se anida; extend() (o +=) fusiona los elementos en su lugar.
JunoAñadir elementosappend() es amortizado O(1), insert(0, x) es O(n), así que construir de frente a atrás en un bucle silenciosamente se vuelve cuadrático, usa deque allá. Y append() una lista la anida completa; extend() o += es la fusión.

Remover elementos

Cuatro herramientas para remover elementos. remove() busca por valor. pop() remueve por posición y te devuelve el elemento. del remueve por posición sin valor de retorno. clear() vacía la lista completa.

remove() es O(n): escanea buscando la primera ocurrencia por valor. pop() sin argumento es O(1) para el último elemento. pop(i) para cualquier otra posición es O(n) porque los elementos se desplazan. del scores[i] es equivalente a pop(i) pero desecha el valor de retorno.

remove(value) escanea desde el frente comparando cada elemento hasta que encuentra una coincidencia, luego desplaza todo después hacia la izquierda: O(n) (el costo crece con la longitud de la lista). pop() del final es O(1), nada se desplaza. pop(i) en cualquier otro lugar es O(n) por la misma razón de desplazamiento. Si te encuentras removiendo de posiciones arbitrarias en un bucle caliente, ese O(n) repetido es la señal de que debes reestructurar los datos o elegir una colección diferente.

python
scores = [87, 92, 74, 65, 91]

scores.remove(74)    # remueve la primera ocurrencia de 74
scores.pop()         # remueve y devuelve el último elemento (91)
scores.pop(0)        # remueve y devuelve el elemento en la posición 0 (87)
del scores[1]        # remueve en la posición 1, sin valor de retorno
scores.clear()       # remueve todo

remove() lanza un ValueError si el valor no está en la lista. Verifica con in primero si no estás seguro:

python
if 74 in scores:
    scores.remove(74)

remove() lanza ValueError si falla. La verificación in añade un escaneo O(n) extra, entonces caminas la lista dos veces. Para código de una sola vez está bien. Cuando preferirías capturar la falla en lugar de pre-verificar, try / except ValueError está cubierto en el capítulo Archivos y excepciones.

El patrón in luego remove() es dos escaneos O(n), la lista caminada dos veces. Cuando el orden no importa, canjear el objetivo con el último elemento y hacer pop es O(1) (costo constante, sin desplazamientos). Y si pruebas membresía mucho, un set busca en O(1) donde una lista es O(n), cubierto en el capítulo Tuplas y conjuntos. La regla de oro: una lista para datos ordenados y cambiantes, un conjunto en el momento en que "¿está en ahí?" es la pregunta caliente.

JunoRemover elementosremove() elimina por valor y solo la primera coincidencia, y lanza ValueError si el valor no está allí, así que verifica con in primero cuando no estés seguro. pop() remueve por posición y devuelve el elemento, del remueve por posición y no te devuelve nada.
JunoRemover elementosremove() elimina la primera coincidencia de valor y lanza ValueError si falla. pop() del final es barato y devuelve el elemento; pop(i) en otro lugar desplaza todo lo que viene después. del remueve por posición, sin retorno.
JunoRemover elementosremove() y pop(i) lejos del final son desplazamientos O(n); solo pop() del final es O(1). Si "¿está en ahí?" es la ruta caliente, un set lo hace en O(1) donde la lista es O(n). Pre-verificar con in antes de remove() camina la lista dos veces.

Ordenamiento

sorted() devuelve una lista nueva ordenada y deja tu original intacta. .sort() ordena la lista en el lugar y devuelve None. Esa diferencia importa más de lo que suena.

sorted() es el predeterminado seguro: nunca modifica el original. .sort() modifica en el lugar y devuelve None, que es una trampa común. Asignar el resultado de .sort() te da None, no la lista ordenada. Usa sorted() cuando necesites el original intacto; usa .sort() cuando solo quieras la versión ordenada.

El ordenamiento de Python es estable: los elementos iguales mantienen su orden relativo original. Eso es lo que te permite ordenar por una clave, luego ordenar nuevamente por otra, y tener el primer ordenamiento sobreviva como un desempate. También es rápido en datos casi-ordenados, cerca de O(n) (lineal) en lugar de O(n log n) de un ordenamiento desde cero, así que re-ordenar una lista mayormente-ordenada es barato. .sort() devuelve None a propósito: un método que cambia la lista en el lugar no también la devuelve, que es la convención que te detiene de escribir x = lst.sort() y obtener silenciosamente None. sorted() toma cualquier iterable, no solo listas, y siempre devuelve una lista nueva.

python
scores = [87, 42, 96, 55, 71]

ranked = sorted(scores)            # [42, 55, 71, 87, 96] (lista nueva)
scores.sort()                      # ordena en el lugar, devuelve None
scores.sort(reverse=True)          # [96, 87, 71, 55, 42]

result = scores.sort()             # result es None, no la lista ordenada
JunoOrdenamientosorted() te da una lista nueva ordenada y deja el original solo. .sort() reorganiza la lista misma y devuelve None, entonces x = scores.sort() te deja con None, no con la lista. Cometí ese error exacto más de una vez antes de que se me quedara.
JunoOrdenamientosorted() es el predeterminado seguro, nunca toca el original. .sort() ordena en el lugar y devuelve None, así que asignar su resultado es la trampa a recordar. Elige sorted() cuando necesites el original intacto.
JunoOrdenamiento El ordenamiento es estable, así que ordena por una clave secundaria primero, luego la primaria, y los empates mantienen su orden anterior. Corre casi-lineal en datos casi-ordenados, así que re-ordenar es barato. .sort() devuelve None por diseño; sorted() toma cualquier iterable y devuelve una lista nueva.

Operaciones útiles

Python tiene un conjunto de herramientas integradas que funcionan directamente en listas. len(), sum(), min(), y max() son los cuatro que alcanzarás constantemente.

Las funciones de secuencia integradas funcionan en cualquier lista. in es un escaneo lineal para listas; si necesitas pruebas de membresía rápidas repetidas, convierte a un conjunto. .index() lanza ValueError si el valor no se encuentra.

len(), sum(), min(), y max() funcionan en cualquier iterable, no solo listas, así que la misma llamada lee una lista, una tupla, o un generador. La nota de costo que importa: in y .index() ambos escanean de frente a atrás, O(n) (el costo crece con la longitud), porque una lista no tiene un índice de sus contenidos. Las pruebas de membresía repetidas en un bucle son donde eso suma, y un set es la solución. Un borde afilado: sum() comienza desde 0, así que suma números, no cadenas; para unir cadenas usa "".join(), cubierto en el capítulo Cadenas.

python
scores = [87, 92, 74, 65, 91]

len(scores)          # 5
sum(scores)          # 409
min(scores)          # 65
max(scores)          # 92
scores.count(87)     # 1
scores.index(74)     # 2
74 in scores         # True
74 not in scores     # False
scores.copy()        # copia superficial
scores.reverse()     # invierte en el lugar
JunoOperaciones útileslen(), sum(), min(), y max() funcionan directamente en una lista, sin configuración. in pregunta si algo está presente, .count() cuenta cuántas veces, y .index() encuentra la posición de la primera coincidencia.
JunoOperaciones útiles Las funciones integradas funcionan en cualquier lista, y las de secuencia funcionan en cualquier iterable. in es un escaneo lineal, así que para pruebas de membresía repetidas convierte a un conjunto. .index() lanza ValueError cuando el valor no se encuentra.
JunoOperaciones útileslen(), sum(), min(), max() toman cualquier iterable, no solo listas. in e .index() son ambos escaneos O(n), así que una prueba de membresía caliente quiere un set. Y sum() comienza desde 0, así que para cadenas alcanza "".join().

Iterando

Un bucle for va a través de una lista un elemento a la vez. La variable después de for recibe cada elemento por turno. Cuando también necesitas la posición, enumerate() te da ambas sin un contador manual.

for item in list invoca el iterador de la lista y lo avanza en cada paso. enumerate(iterable, start=0) envuelve el iterador y produce pares (index, value). Usar enumerate() es más limpio y menos propenso a errores que mantener una variable contador.

enumerate() produce pares (index, value) conforme iteras, lo cual es por qué for i, item in enumerate(...) lee limpiamente: cada par se desempaca en los dos nombres. El argumento start desplaza el contador solo para la pantalla (así start=1 numera desde uno) sin cambiar dónde el bucle realmente lee en la lista. La razón para preferirlo sobre un contador hecho a mano no es velocidad, es que un contador separado que incrementas a mano es una cosa más que olvidas incrementar, y enumerate() no puede desincronizarse.

python
players = ["María", "Carlos", "Juan"]

for player in players:
    print(player)

for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")
# 1. María
# 2. Carlos
# 3. Juan

for y enumerate() obtienen tratamiento completo en el capítulo Control de flujo. La versión corta: for player in players corre una vez por elemento, y enumerate() te da tanto la posición como el valor en cada iteración.

JunoIterando Un bucle for camina la lista un elemento a la vez, y el nombre después de for se convierte en ese elemento cada turno. Cuando también quieres la posición, enumerate() te entrega ambas, así nunca tienes que mantener un contador tu mismo. Dejar que contara por mí me ahorró un montón de errores de uno más.
JunoIterandofor item in list visita cada elemento en orden. enumerate(iterable, start=0) produce pares (index, value), más limpio y menos propenso a errores que un contador que manejas a mano. Usa start=1 cuando quieras numeración basada en uno.
JunoIterandoenumerate() produce pares (index, value) que se desempaca directamente en dos nombres, y start solo desplaza el contador mostrado, no la posición de lectura. La ganancia no es velocidad, es que un contador manual puede desincronizarse y este no.

Listas anidadas

Una lista puede contener otras listas. Esta es la forma de representar una cuadrícula o una tabla: una lista de filas, cada fila siendo una lista de valores. Dos juegos de corchetes cuadrados acceden a un elemento: el primero elige la fila, el segundo elige la columna.

Las listas anidadas son listas de referencias a listas. Cada lista interna es un objeto independiente. Accede con subscriptos encadenados: grid[row][col]. Mutar una lista interna afecta la lista externa porque la lista externa mantiene una referencia al mismo objeto.

Una lista anidada no es un verdadero arreglo 2D. La lista externa contiene referencias a listas internas, cada una su propio objeto independiente, así las filas pueden ser de longitudes diferentes y contener tipos diferentes. grid[1][2] son dos búsquedas: elige la fila, luego indexa en ella. La consecuencia que atrapa a la gente: una copia superficial de la lista externa (el tipo que .copy() hace) duplica el contenedor externo pero aún apunta a las mismas listas internas, así un cambio a una fila interna aparece en ambas copias. La sección de mutabilidad abajo lo hace concreto.

python
grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

grid[0]       # [1, 2, 3]
grid[1][2]    # 6  (fila 1, columna 2)
JunoListas anidadas Una lista puede contener otras listas, que es cómo construyes una cuadrícula o tabla: una lista de filas, cada fila una lista. Dos juegos de corchetes llegan adentro, grid[1][2], el primero elige la fila, el segundo elige de ella.
JunoListas anidadas Las listas anidadas son listas de referencias a otras listas, cada lista interna su propio objeto. Llega dentro con subscriptos encadenados, grid[row][col]. Porque la lista externa solo contiene referencias, mutar una lista interna aparece a través de cada nombre apuntándola.
JunoListas anidadas No un verdadero arreglo 2D: la lista externa contiene referencias, así las filas internas pueden variar en longitud y tipo, y grid[1][2] son dos búsquedas. Una copia superficial duplica la lista externa pero comparte las internas, que es el gotcha en el que la próxima sección aterriza.

Mutabilidad: el gotcha

Esto sorprende a casi todos. Asignar una lista a una nueva variable no hace una copia. Ambos nombres apuntan a la misma lista. Cambia una y cambias la otra. Para obtener una copia independiente, tienes que pedirla explícitamente.

La asignación de lista copia la referencia, no el objeto. Ambos nombres apuntan a la misma lista subyacente. Las mutaciones a través de cualquier nombre afectan los mismos datos. Cuando necesitas datos independientes, copia explícitamente con .copy(), list(), o una división completa [:].

b = a apunta un segundo nombre al mismo objeto de lista, así cualquier cambio a través de b aterriza en el objeto que a también ve. .copy() y a[:] hacen una copia superficial: una nueva lista externa manteniendo las mismas referencias de elementos que el original tenía. Para una lista plana de valores inmutables (números, cadenas, cosas que no pueden cambiar en el lugar) eso es todo lo que necesitas. Para listas anidadas es una trampa, porque las listas internas aún se comparten entre el original y la copia, así editar una fila a través de una aparece en la otra.

python
a = [1, 2, 3]
b = a            # b no es una copia; apunta a la misma lista

b.append(4)
print(a)         # [1, 2, 3, 4]  (cambiado: a y b son la misma lista)
python
b = a.copy()    # copia independiente
b = list(a)     # mismo resultado
b = a[:]        # también lo mismo

# Las listas anidadas aún comparten sus objetos internos:
matrix = [[1, 2], [3, 4]]
copy = matrix.copy()

copy[0].append(99)
print(matrix)   # [[1, 2, 99], [3, 4]]  (lista interna fue compartida)

Para estructuras anidadas donde necesitas independencia completa, copia cada lista interna manualmente, o usa copy.deepcopy() de la biblioteca estándar, cubierto en el capítulo Módulos.

JunoMutabilidad: el gotchab = a no hace una copia, ambos nombres apuntan a la misma lista, así un cambio a través de uno aparece a través del otro. Para una lista independiente tienes que pedirla: .copy(), list(a), o a[:]. Este sorprende a casi todos la primera vez, a mí incluido.
JunoMutabilidad: el gotcha La asignación copia la referencia, no la lista, así b = a deja dos nombres en un objeto y las mutaciones a través de cualquiera golpean los mismos datos. Copia explícitamente con .copy(), list(a), o a[:] cuando necesites independencia.
JunoMutabilidad: el gotchab = a comparte un objeto; .copy() y a[:] hacen una copia superficial, una nueva lista externa sobre las mismas referencias internas. Las listas planas de valores inmutables son seguras de esa forma, las anidadas comparten sus filas, así alcanza copy.deepcopy() cuando también necesitas la independencia interna.

Más métodos

MétodoLo que hace
.append(item)Añade al final
.insert(i, item)Inserta en la posición i
.extend(iterable)Añade todos los elementos de un iterable
.remove(value)Remueve la primera ocurrencia del valor
.pop(i)Remueve y devuelve el elemento en la posición i (predeterminado: último)
.clear()Remueve todos los elementos
.index(value)Posición de la primera ocurrencia
.count(value)Número de ocurrencias
.sort()Ordena en el lugar
.reverse()Invierte en el lugar
.copy()Devuelve una copia superficial

En la práctica

Construyendo un rastreador de puntuaciones: añade resultados, ordénalos, e imprime un resumen.

python
scores = []

scores.append(87)
scores.append(54)
scores.append(92)
scores.append(67)
scores.append(45)

scores.sort(reverse=True)

print(f"Puntuaciones clasificadas: {scores}")
print(f"Mayor:   {scores[0]}")
print(f"Menor:   {scores[-1]}")
print(f"Promedio: {sum(scores) / len(scores):.1f}")
print(f"Top 3:   {scores[:3]}")

Dos listas paralelas de nombres y puntuaciones: encuentra el mejor rendidor e imprime los resultados clasificados.

python
names = ["María", "Carlos", "Carol", "Diego"]
scores = [87, 74, 92, 55]

best_score = max(scores)
best_index = scores.index(best_score)
best_player = names[best_index]

print(f"Mejor jugador: {best_player} ({best_score})")
print(f"Promedio:      {sum(scores) / len(scores):.1f}")

ranked = sorted(scores, reverse=True)
print(f"Distribución (clasificado): {ranked}")

for rank, score in enumerate(ranked, start=1):
    print(f"  Rango {rank}: {score}")

Demostrando la diferencia entre alias y copia, y entre copias superficiales y profundas de listas anidadas.

python
# Aliasing: b no es una copia
a = [1, 2, 3]
b = a
b.append(4)
print(a)    # [1, 2, 3, 4]  (mismo objeto)

# Copia superficial: la lista externa es independiente, las internas se comparten
matrix = [[1, 2, 3], [4, 5, 6]]
shallow = matrix.copy()
shallow[0].append(99)
print(matrix)    # [[1, 2, 3, 99], [4, 5, 6]]  (lista interna compartida)

# Copia profunda manual con un bucle for (sin importaciones necesarias)
matrix = [[1, 2, 3], [4, 5, 6]]
deep_copy = []
for row in matrix:
    deep_copy.append(row[:])    # copia cada lista interna explícitamente

deep_copy[0].append(99)
print(matrix)    # [[1, 2, 3], [4, 5, 6]]  (sin cambios)