Python & AI Tutorials Logo
Programación Python

14. Listas: colecciones ordenadas de elementos

Hasta ahora en este libro, hemos trabajado con piezas individuales de datos: números sueltos, cadenas y valores booleanos. Pero los programas reales a menudo necesitan trabajar con colecciones de elementos relacionados: una lista de nombres de estudiantes, una serie de lecturas de temperatura, una colección de precios de productos o una secuencia de comandos de usuario. La lista (list) de Python es la herramienta fundamental para almacenar y trabajar con colecciones ordenadas de datos.

Una lista es una secuencia (sequence) que puede contener múltiples elementos en un orden específico. A diferencia de las cadenas (que solo pueden contener caracteres), las listas pueden contener cualquier tipo de dato: números, cadenas, booleanos o incluso otras listas. Las listas también son mutables, lo que significa que puedes cambiar su contenido después de crearlas: añadir elementos, eliminar elementos o modificar los existentes.

En este capítulo, exploraremos cómo crear listas, acceder a sus elementos, modificarlas y usarlas para resolver problemas prácticos de programación. Al final, entenderás por qué las listas son una de las estructuras de datos más potentes y utilizadas con más frecuencia en Python.

14.1) Crear listas y acceder a elementos

14.1.1) Crear listas con corchetes

La forma más común de crear una lista es encerrando los elementos entre corchetes [], con los elementos separados por comas. Aquí tienes un ejemplo sencillo:

python
# Una lista de nombres de estudiantes
students = ["Alice", "Bob", "Charlie", "Diana"]
print(students)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']

Fíjate en cómo Python muestra la lista: muestra los corchetes y pone comillas alrededor de cada cadena. Esta es la representación de la lista: cómo Python te muestra lo que hay dentro.

Las listas pueden contener cualquier tipo de dato. Aquí tienes una lista de notas de examen:

python
# Una lista de puntuaciones enteras
scores = [85, 92, 78, 95, 88]
print(scores)  # Output: [85, 92, 78, 95, 88]

Incluso puedes mezclar distintos tipos en la misma lista, aunque esto es menos común en la práctica:

python
# Una lista de tipos mezclados (menos común pero válida)
mixed_data = ["Alice", 25, True, 3.14]
print(mixed_data)  # Output: ['Alice', 25, True, 3.14]

Una lista vacía no contiene elementos y se crea solo con los corchetes:

python
# Una lista vacía
empty = []
print(empty)  # Output: []
print(len(empty))  # Output: 0

La función len(), que hemos usado con cadenas, también funciona con listas: devuelve el número de elementos de la lista.

14.1.2) Comprender el orden de las listas y las posiciones

Las listas mantienen el orden en el que añades los elementos. El primer elemento que pones se queda primero, el segundo se queda segundo, y así sucesivamente. Este orden es crucial porque te permite acceder a elementos específicos por su posición (también llamada su índice (index)).

Python usa indexación basada en cero (zero-based indexing): el primer elemento está en la posición 0, el segundo en la posición 1, y así sucesivamente. Esto puede parecer inusual al principio, pero es una convención utilizada por muchos lenguajes de programación.

Lista: ['Alice', 'Bob', 'Charlie', 'Diana']

Índice 0: 'Alice'

Índice 1: 'Bob'

Índice 2: 'Charlie'

Índice 3: 'Diana'

Veamos cómo funciona esto en la práctica:

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
# Acceder al primer estudiante (índice 0)
first_student = students[0]
print(first_student)  # Output: Alice
 
# Acceder al tercer estudiante (índice 2)
third_student = students[2]
print(third_student)  # Output: Charlie

Fíjate en que, para obtener el tercer estudiante, usamos el índice 2, no el 3. Esto se debe a que el conteo empieza en 0.

14.1.3) Acceder a elementos con índices positivos

Para acceder a un elemento de una lista, escribe el nombre de la lista seguido del índice entre corchetes: list_name[index]. El índice debe ser un entero dentro del rango válido (0 a len(list) - 1).

Aquí tienes un ejemplo práctico trabajando con precios de productos:

python
# Precios de productos en dólares
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
 
# Acceder a precios específicos
first_price = prices[0]
last_index = len(prices) - 1  # Calcular el último índice válido
last_price = prices[last_index]
 
print(f"First product costs: ${first_price}")  # Output: First product costs: $19.99
print(f"Last product costs: ${last_price}")    # Output: Last product costs: $8.99

¿Por qué usamos len(prices) - 1 para el último índice? Porque si una lista tiene 5 elementos, los índices son 0, 1, 2, 3, 4: el último índice válido siempre es uno menos que la longitud.

También puedes usar índices en expresiones y cálculos:

python
scores = [85, 92, 78, 95, 88]
 
# Calcular el promedio de las tres primeras puntuaciones
first_three_average = (scores[0] + scores[1] + scores[2]) / 3
print(f"Average of first three: {first_three_average}")  # Output: Average of first three: 85.0

14.1.4) Índices negativos: contar desde el final

Python proporciona una característica conveniente: los índices negativos te permiten acceder a elementos desde el final de la lista. El índice -1 se refiere al último elemento, -2 al penúltimo, y así sucesivamente.

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
# Acceder desde el final
last_student = students[-1]
second_to_last = students[-2]
 
print(last_student)      # Output: Diana
print(second_to_last)    # Output: Charlie

Esto es especialmente útil cuando quieres el último elemento pero no quieres calcular len(list) - 1:

python
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
 
# Estos dos enfoques son equivalentes
last_price_method1 = prices[len(prices) - 1]
last_price_method2 = prices[-1]
 
print(last_price_method1)  # Output: 8.99
print(last_price_method2)  # Output: 8.99

Así es como los índices positivos y negativos se mapean a los mismos elementos:

Lista: ['Alice', 'Bob', 'Charlie', 'Diana']

Positivos: 0, 1, 2, 3

Negativos: -4, -3, -2, -1

Ambos se refieren a los mismos elementos

14.1.5) Qué ocurre con índices no válidos

Si intentas acceder a un índice que no existe, Python lanza un IndexError:

python
students = ["Alice", "Bob", "Charlie"]
 
# ADVERTENCIA: Esta lista tiene índices 0, 1, 2 (o -3, -2, -1) - solo para demostración
# Intentar acceder al índice 3 provoca un error
# PROBLEMA: El índice 3 no existe en una lista de 3 elementos
# print(students[3])  # IndexError: list index out of range

Este error es la forma que tiene Python de decirte que has pedido un elemento que no está ahí.

14.2) Indexación y slicing de listas

14.2.1) Comprender los fundamentos del slicing de listas

Al igual que podemos hacer slicing de cadenas (como aprendimos en el Capítulo 5), podemos hacer slices (slices) de listas para extraer partes de ellas. Un slice crea una nueva lista que contiene un subconjunto de los elementos de la lista original. La sintaxis es list[start:stop], donde start es el índice donde comienza el slice (inclusivo) y stop es donde termina (exclusivo).

python
numbers = [10, 20, 30, 40, 50, 60, 70]
 
# Obtener elementos desde el índice 1 hasta (pero sin incluir) el índice 4
subset = numbers[1:4]
print(subset)  # Output: [20, 30, 40]

El slice [1:4] incluye los índices 1, 2 y 3, pero se detiene antes del índice 4. Esta regla de “stop es exclusivo” es la misma que con el slicing de cadenas.

Veamos un ejemplo práctico con nombres de estudiantes:

python
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
 
# Obtener los tres primeros estudiantes
first_three = students[0:3]
print(first_three)  # Output: ['Alice', 'Bob', 'Charlie']
 
# Obtener estudiantes desde el índice 2 hasta el 4
middle_group = students[2:5]
print(middle_group)  # Output: ['Charlie', 'Diana', 'Eve']

14.2.2) Omitir start o stop en slices

Puedes omitir el índice de inicio para cortar desde el comienzo, u omitir el índice de fin para cortar hasta el final:

python
scores = [85, 92, 78, 95, 88, 91, 87]
 
# Desde el comienzo hasta el índice 3
first_few = scores[:3]
print(first_few)  # Output: [85, 92, 78]
 
# Desde el índice 4 hasta el final
last_few = scores[4:]
print(last_few)  # Output: [88, 91, 87]
 
# La lista completa (del principio al final)
all_scores = scores[:]
print(all_scores)  # Output: [85, 92, 78, 95, 88, 91, 87]

El slice [:] crea una copia de toda la lista. Esto es útil cuando quieres trabajar con un duplicado sin modificar el original; exploraremos esto más en la sección 14.6.

14.2.3) Usar índices negativos en slices

Los índices negativos funcionan en los slices igual que con el acceso a un solo elemento. Esto es especialmente útil para obtener elementos desde el final:

python
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
 
# Obtener los tres últimos estudiantes
last_three = students[-3:]
print(last_three)  # Output: ['Diana', 'Eve', 'Frank']
 
# Obtener todo excepto los dos últimos estudiantes
all_but_last_two = students[:-2]
print(all_but_last_two)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']
 
# Obtener desde el antepenúltimo hasta el penúltimo
middle_from_end = students[-3:-1]
print(middle_from_end)  # Output: ['Diana', 'Eve']

14.2.4) Slicing con un valor step

Puedes añadir un tercer parámetro para controlar el paso (cuántos índices saltar entre elementos). La sintaxis completa es list[start:stop:step]:

python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Cada segundo número empezando desde el índice 0
evens = numbers[0:10:2]
print(evens)  # Output: [0, 2, 4, 6, 8]
 
# Cada tercer número empezando desde el índice 1
every_third = numbers[1:10:3]
print(every_third)  # Output: [1, 4, 7]

También puedes usar un paso negativo para invertir la lista:

python
numbers = [1, 2, 3, 4, 5]
 
# Invertir la lista
reversed_numbers = numbers[::-1]
print(reversed_numbers)  # Output: [5, 4, 3, 2, 1]

El slice [::-1] significa “empezar al final, ir hasta el principio, avanzando hacia atrás de 1 en 1”. Este es un modismo común de Python para invertir secuencias.

14.2.5) Los slices nunca provocan IndexError

A diferencia de acceder a un solo elemento, el slicing es muy tolerante. Si especificas índices fuera del rango de la lista, Python simplemente los ajusta para que encajen:

python
numbers = [10, 20, 30, 40, 50]
 
# Pedir más de lo que existe
extended_slice = numbers[2:100]
print(extended_slice)  # Output: [30, 40, 50]
 
# Empezar más allá del final
empty_slice = numbers[10:20]
print(empty_slice)  # Output: []

Este comportamiento es útil porque significa que no tienes que preocuparte por límites exactos al hacer slicing: Python gestiona los casos borde de forma elegante.

14.3) Modificar listas y métodos comunes de listas

14.3.1) Las listas son mutables: cambiar elementos

A diferencia de las cadenas, que son inmutables, las listas son mutables: puedes cambiar su contenido después de crearlas. Puedes modificar elementos individuales asignando nuevos valores a índices específicos:

python
# Empezar con una lista de precios
prices = [19.99, 24.50, 15.75, 32.00]
print(prices)  # Output: [19.99, 24.5, 15.75, 32.0]
 
# Actualizar el segundo precio (índice 1)
prices[1] = 22.99
print(prices)  # Output: [19.99, 22.99, 15.75, 32.0]
 
# Actualizar el último precio usando indexación negativa
prices[-1] = 29.99
print(prices)  # Output: [19.99, 22.99, 15.75, 29.99]

Esta mutabilidad es potente: significa que puedes actualizar los datos directamente, sin crear nuevas listas. Sin embargo, también significa que necesitas tener cuidado con cambios no intencionados, que trataremos en la sección 14.6.

14.3.2) Añadir elementos con append()

El método append() añade un único elemento al final de una lista. Esta es una de las operaciones más usadas con listas:

python
# Empezar con un carrito de compra vacío
cart = []
print(cart)  # Output: []
 
# Añadir elementos uno a uno
cart.append("Milk")
print(cart)  # Output: ['Milk']
 
cart.append("Bread")
print(cart)  # Output: ['Milk', 'Bread']
 
cart.append("Eggs")
print(cart)  # Output: ['Milk', 'Bread', 'Eggs']

Fíjate en que append() modifica la lista en el sitio: no devuelve una lista nueva. El método devuelve None, así que no necesitas asignar su resultado:

python
scores = [85, 92, 78]
result = scores.append(95)
 
print(scores)   # Output: [85, 92, 78, 95]
print(result)   # Output: None

14.3.3) Insertar elementos en posiciones específicas con insert()

Mientras que append() siempre añade al final, insert() te permite añadir un elemento en cualquier posición. La sintaxis es list.insert(index, item):

python
students = ["Alice", "Charlie", "Diana"]
print(students)  # Output: ['Alice', 'Charlie', 'Diana']
 
# Insertar "Bob" en el índice 1 (entre Alice y Charlie)
students.insert(1, "Bob")
print(students)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']

Cuando insertas en un índice, el elemento que está actualmente en esa posición (y todos los elementos después) se desplazan a la derecha:

python
numbers = [10, 20, 30, 40]
print(numbers)  # Output: [10, 20, 30, 40]
 
# Insertar 25 en el índice 2
numbers.insert(2, 25)
print(numbers)  # Output: [10, 20, 25, 30, 40]

Puedes insertar al principio usando el índice 0:

python
priorities = ["Medium", "Low"]
priorities.insert(0, "High")
print(priorities)  # Output: ['High', 'Medium', 'Low']

Si especificas un índice más allá de la longitud de la lista, insert() simplemente añade el elemento al final (como append()):

python
items = [1, 2, 3]
items.insert(100, 4)
print(items)  # Output: [1, 2, 3, 4]

14.3.4) Eliminar elementos con remove()

El método remove() elimina la primera aparición de un valor específico de la lista:

python
fruits = ["apple", "banana", "cherry", "banana", "date"]
print(fruits)  # Output: ['apple', 'banana', 'cherry', 'banana', 'date']
 
# Eliminar el primer "banana"
fruits.remove("banana")
print(fruits)  # Output: ['apple', 'cherry', 'banana', 'date']

Fíjate en que solo se eliminó el primer "banana": el segundo permanece. Si intentas eliminar un valor que no existe, Python lanza un ValueError:

python
numbers = [10, 20, 30]
# ADVERTENCIA: Intentar eliminar un valor inexistente - solo para demostración
# PROBLEMA: 40 no está en la lista
# numbers.remove(40)  # ValueError: list.remove(x): x not in list

Para evitar este error, puedes comprobar si el elemento existe antes de eliminarlo:

python
cart = ["Milk", "Bread", "Eggs"]
item_to_remove = "Butter"
 
if item_to_remove in cart:
    cart.remove(item_to_remove)
    print(f"Removed {item_to_remove}")
else:
    print(f"{item_to_remove} not in cart")
# Output: Butter not in cart

14.3.5) Eliminar y devolver elementos con pop()

El método pop() elimina un elemento en un índice específico y lo devuelve. Si no especificas un índice, elimina y devuelve el último elemento:

python
scores = [85, 92, 78, 95, 88]
 
# Eliminar y obtener la última puntuación
last_score = scores.pop()
print(f"Removed: {last_score}")  # Output: Removed: 88
print(scores)  # Output: [85, 92, 78, 95]
 
# Eliminar y obtener la puntuación del índice 1
second_score = scores.pop(1)
print(f"Removed: {second_score}")  # Output: Removed: 92
print(scores)  # Output: [85, 78, 95]

Esto es útil cuando necesitas procesar elementos de una lista uno a uno:

python
tasks = ["Write code", "Test code", "Deploy code"]
 
while len(tasks) > 0:
    current_task = tasks.pop(0)  # Eliminar desde el principio
    print(f"Working on: {current_task}")
 
# Output:
# Working on: Write code
# Working on: Test code
# Working on: Deploy code
 
print(tasks)  # Output: []

14.3.6) Ampliar listas con extend()

El método extend() añade todos los elementos de otra lista (o de cualquier iterable) al final de la lista actual:

python
primary_colors = ["red", "blue", "yellow"]
secondary_colors = ["green", "orange", "purple"]
 
# Añadir todos los colores secundarios a los primarios
primary_colors.extend(secondary_colors)
print(primary_colors)
# Output: ['red', 'blue', 'yellow', 'green', 'orange', 'purple']

Esto es diferente de append(), que añadiría la lista completa como un único elemento:

python
colors1 = ["red", "blue"]
colors2 = ["green", "orange"]
 
# Usando append (añade la lista como un elemento)
colors1.append(colors2)
print(colors1)  # Output: ['red', 'blue', ['green', 'orange']]
 
# Usando extend (añade cada elemento individualmente)
colors3 = ["red", "blue"]
colors3.extend(colors2)
print(colors3)  # Output: ['red', 'blue', 'green', 'orange']

14.3.7) Ordenar listas con sort() y sorted()

Python proporciona dos formas de ordenar listas. El método sort() ordena la lista en el sitio (modificando el original):

python
scores = [78, 95, 85, 92, 88]
scores.sort()
print(scores)  # Output: [78, 85, 88, 92, 95]

Para ordenar en orden descendente, usa el parámetro reverse:

python
scores = [78, 95, 85, 92, 88]
scores.sort(reverse=True)
print(scores)  # Output: [95, 92, 88, 85, 78]

La función sorted() (que exploraremos más en el Capítulo 38) crea una nueva lista ordenada sin modificar el original:

python
original = [78, 95, 85, 92, 88]
sorted_scores = sorted(original)
 
print(original)       # Output: [78, 95, 85, 92, 88]
print(sorted_scores)  # Output: [78, 85, 88, 92, 95]

El ordenamiento también funciona con cadenas, usando orden alfabético:

python
names = ["Charlie", "Alice", "Diana", "Bob"]
names.sort()
print(names)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']

14.3.8) Invertir listas con reverse()

El método reverse() invierte la lista en el sitio:

python
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers)  # Output: [5, 4, 3, 2, 1]

Esto es diferente de ordenar en orden inverso: reverse() simplemente invierte el orden actual, sea cual sea:

python
mixed = [3, 1, 4, 1, 5]
mixed.reverse()
print(mixed)  # Output: [5, 1, 4, 1, 3]

Recuerda que también puedes invertir una lista usando slicing: list[::-1]. La diferencia es que el slicing crea una lista nueva, mientras que reverse() modifica el original.

14.3.9) Encontrar elementos con index() y count()

El método index() devuelve la posición de la primera aparición de un valor:

python
students = ["Alice", "Bob", "Charlie", "Diana", "Bob"]
 
# Encontrar dónde está "Charlie"
position = students.index("Charlie")
print(f"Charlie is at index {position}")  # Output: Charlie is at index 2
 
# Encontrar el primer "Bob"
bob_position = students.index("Bob")
print(f"Bob is at index {bob_position}")  # Output: Bob is at index 1

Si el valor no existe, index() lanza un ValueError:

python
students = ["Alice", "Bob", "Charlie"]
# ADVERTENCIA: Intentar encontrar un valor inexistente - solo para demostración
# PROBLEMA: 'Eve' no está en la lista
# position = students.index("Eve")  # ValueError: 'Eve' is not in list

El método count() devuelve cuántas veces aparece un valor:

python
numbers = [1, 2, 3, 2, 4, 2, 5]
twos = numbers.count(2)
print(f"The number 2 appears {twos} times")  # Output: The number 2 appears 3 times
 
# Count puede devolver 0 si el elemento no existe
sixes = numbers.count(6)
print(f"The number 6 appears {sixes} times")  # Output: The number 6 appears 0 times

14.3.10) Vaciar todos los elementos con clear()

El método clear() elimina todos los elementos de una lista, dejándola vacía:

python
cart = ["Milk", "Bread", "Eggs", "Butter"]
print(cart)  # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
 
cart.clear()
print(cart)  # Output: []
print(len(cart))  # Output: 0

Esto equivale a asignar una lista vacía, pero clear() es más explícito sobre la intención.

14.4) Eliminar elementos de listas con del

14.4.1) Usar del para eliminar elementos por índice

La sentencia del puede eliminar elementos de una lista en índices específicos:

python
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]
print(students)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
 
# Eliminar el elemento en el índice 2
del students[2]
print(students)  # Output: ['Alice', 'Bob', 'Diana', 'Eve']

A diferencia de pop(), del no devuelve el valor eliminado: simplemente lo borra. Esto es útil cuando quieres eliminar un elemento pero no necesitas usarlo:

python
scores = [85, 92, 78, 95, 88]
 
# Eliminar la puntuación más baja (en el índice 2)
del scores[2]
print(scores)  # Output: [85, 92, 95, 88]

También puedes usar índices negativos con del:

python
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]
 
# Eliminar la última tarea
del tasks[-1]
print(tasks)  # Output: ['Task 1', 'Task 2', 'Task 3']

14.4.2) Eliminar slices con del

La sentencia del puede eliminar slices enteros de una sola vez:

python
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers)  # Output: [10, 20, 30, 40, 50, 60, 70]
 
# Eliminar elementos del índice 2 al 4 (índices 2, 3, 4)
del numbers[2:5]
print(numbers)  # Output: [10, 20, 60, 70]

Esto es particularmente útil para eliminar rangos de elementos:

python
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
# Eliminar los tres primeros elementos
del data[:3]
print(data)  # Output: [4, 5, 6, 7, 8, 9, 10]
 
# Eliminar los dos últimos elementos
del data[-2:]
print(data)  # Output: [4, 5, 6, 7, 8]

Incluso puedes eliminar uno de cada dos elementos usando slicing con step:

python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Eliminar cada segundo elemento
del numbers[::2]
print(numbers)  # Output: [1, 3, 5, 7, 9]

14.4.3) Comparar del, remove() y pop()

Aclaremos cuándo usar cada método de eliminación:

python
# Lista de ejemplo para comparar
items = ["apple", "banana", "cherry", "date", "elderberry"]
 
# Usa remove() cuando conoces el VALOR a eliminar
items_copy1 = items.copy()
items_copy1.remove("cherry")  # Elimina el primer "cherry"
print(items_copy1)  # Output: ['apple', 'banana', 'date', 'elderberry']
 
# Usa pop() cuando conoces el ÍNDICE y necesitas el valor
items_copy2 = items.copy()
removed_item = items_copy2.pop(2)  # Elimina y devuelve el elemento del índice 2
print(f"Removed: {removed_item}")  # Output: Removed: cherry
print(items_copy2)  # Output: ['apple', 'banana', 'date', 'elderberry']
 
# Usa del cuando conoces el ÍNDICE pero no necesitas el valor
items_copy3 = items.copy()
del items_copy3[2]  # Solo elimina el elemento del índice 2
print(items_copy3)  # Output: ['apple', 'banana', 'date', 'elderberry']

14.5) Iterar sobre listas con bucles for

14.5.1) Iteración básica de listas

Una de las operaciones más comunes con listas es procesar cada elemento en secuencia. El bucle for(for loop) (que aprendimos en el Capítulo 12) es perfecto para esto:

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
# Procesar cada estudiante
for student in students:
    print(f"Hello, {student}!")
 
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
# Hello, Diana!

La variable del bucle (student en este caso) toma cada valor de la lista, uno a uno, en orden. Puedes llamar a esta variable como quieras, con un nombre significativo:

python
scores = [85, 92, 78, 95, 88]
 
# Calcular y mostrar la calificación de cada puntuación
for score in scores:
    if score >= 90:
        grade = "A"
    elif score >= 80:
        grade = "B"
    else:
        grade = "C"
    print(f"Score {score} is a {grade}")
 
# Output:
# Score 85 is a B
# Score 92 is a A
# Score 78 is a C
# Score 95 is a A
# Score 88 is a B

14.5.2) Procesar elementos correspondientes de múltiples listas

A veces necesitas trabajar con datos relacionados almacenados en listas separadas. Aprenderemos sobre la función zip() en detalle en el Capítulo 38, pero aquí tienes un breve avance de cómo puede ayudar a procesar elementos correspondientes:

python
# Aprenderemos sobre zip() en el Capítulo 38, pero por ahora, aquí tienes un ejemplo simple
students = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
 
# Procesar pares correspondientes
for student, score in zip(students, scores):
    print(f"{student} scored {score}")
 
# Output:
# Alice scored 85
# Bob scored 92
# Charlie scored 78

La función zip() empareja elementos de múltiples listas, lo cual es útil cuando tienes datos relacionados en listas separadas. Exploraremos esto y otras herramientas de iteración en profundidad en el Capítulo 38.

14.6) Copiar listas y evitar referencias compartidas

14.6.1) Comprender las referencias de listas

Cuando asignas una lista a una variable, Python no crea una copia de la lista: crea una referencia al mismo objeto lista en memoria. Esto significa que múltiples variables pueden referirse a la misma lista:

python
original = [1, 2, 3]
reference = original  # Ambas variables apuntan a la MISMA lista
 
# Modificar a través de una variable afecta a la otra
reference.append(4)
print(original)   # Output: [1, 2, 3, 4]
print(reference)  # Output: [1, 2, 3, 4]

Este comportamiento puede sorprenderte si esperas que reference sea una copia independiente. Veamos por qué esto importa:

python
# Escenario: Quieres hacer seguimiento de cambios en un carrito de compra
cart = ["Milk", "Bread"]
backup = cart  # Intentar guardar el estado original
 
# Añadir más artículos
cart.append("Eggs")
cart.append("Butter")
 
# Comprobar el "backup"
print(backup)  # Output: ['Milk', 'Bread', 'Eggs', 'Butter']

¡El backup también cambió! Esto se debe a que backup y cart son dos nombres para el mismo objeto lista.

Variable: cart

Objeto lista: ⦗'Milk', 'Bread', 'Eggs', 'Butter'⦘

Variable: backup

14.6.2) Crear copias independientes con slicing

Para crear una copia verdaderamente independiente, usa slicing con [:]:

python
original = [1, 2, 3]
copy = original[:]  # Crea una lista NUEVA con el mismo contenido
 
# Modificar la copia no afecta al original
copy.append(4)
print(original)  # Output: [1, 2, 3]
print(copy)      # Output: [1, 2, 3, 4]

Ahora arreglemos nuestro ejemplo del carrito:

python
cart = ["Milk", "Bread"]
backup = cart[:]  # Crear una copia independiente
 
# Añadir más artículos al carrito
cart.append("Eggs")
cart.append("Butter")
 
# El backup permanece sin cambios
print(cart)    # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
print(backup)  # Output: ['Milk', 'Bread']

14.6.3) Crear copias con el método copy()

Las listas también tienen un método copy() que hace lo mismo que [:]:

python
original = [10, 20, 30]
copy = original.copy()
 
copy.append(40)
print(original)  # Output: [10, 20, 30]
print(copy)      # Output: [10, 20, 30, 40]

Tanto [:] como copy() crean copias superficiales (shallow copies), lo cual veremos a continuación.

14.6.4) La limitación de la copia superficial

Tanto [:] como copy() crean copias superficiales. Esto significa que copian la estructura de la lista, pero si la lista contiene otros objetos mutables (como otras listas), esos objetos internos siguen compartiéndose:

python
# Una lista que contiene listas
original = [[1, 2], [3, 4], [5, 6]]
copy = original[:]
 
# Modificar la estructura de la lista externa es independiente
copy.append([7, 8])
print(original)  # Output: [[1, 2], [3, 4], [5, 6]]
print(copy)      # Output: [[1, 2], [3, 4], [5, 6], [7, 8]]
 
# ¡Pero modificar una lista interna afecta a ambas!
copy[0].append(99)
print(original)  # Output: [[1, 2, 99], [3, 4], [5, 6]]
print(copy)      # Output: [[1, 2, 99], [3, 4], [5, 6], [7, 8]]

¿Por qué ocurre esto? Porque la copia superficial crea una nueva lista externa, pero las listas internas siguen siendo referencias compartidas:

lista externa original

Lista interna: ⦗1, 2, 99⦘

Lista interna: ⦗3, 4⦘

Lista interna: ⦗5, 6⦘

lista externa copy

Lista interna: ⦗7, 8⦘

Para estructuras anidadas, necesitarías una copia profunda (deep copy), que aprenderemos cuando exploremos el módulo copy en capítulos posteriores. Por ahora, ten en cuenta que las copias superficiales funcionan perfectamente para listas de elementos inmutables (números, cadenas, tuplas), pero requieren precaución con estructuras mutables anidadas.

14.6.5) Cuándo las referencias compartidas son útiles

A veces quieres que múltiples variables se refieran a la misma lista. Esto es útil cuando necesitas modificar una lista desde diferentes partes de tu código:

python
# Una función que modifica una lista en el sitio
def add_bonus_points(scores, bonus):
    for i in range(len(scores)):
        scores[i] = scores[i] + bonus
 
# Se modifica la lista original
student_scores = [85, 92, 78]
add_bonus_points(student_scores, 5)
print(student_scores)  # Output: [90, 97, 83]

Esto funciona porque la función recibe una referencia a la lista original, no una copia. Exploraremos esto más cuando estudiemos funciones en detalle en la Parte V.

14.7) Usar enumerate() al recorrer listas

14.7.1) La necesidad de índice y valor

A veces, al iterar sobre una lista, necesitas tanto el índice como el valor. Un enfoque es usar range(len(list)):

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
for i in range(len(students)):
    print(f"Student {i}: {students[i]}")
 
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Diana

Esto funciona, pero no es muy elegante. Tienes que usar students[i] para acceder a cada valor, lo cual es menos legible que iterar directamente sobre los valores.

14.7.2) Usar enumerate() para un código más limpio

La función enumerate() proporciona una mejor solución. Devuelve tanto el índice como el valor de cada elemento:

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
for index, student in enumerate(students):
    print(f"Student {index}: {student}")
 
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Diana

La sintaxis for index, value in enumerate(list) desempaqueta cada par que produce enumerate(). Esto es mucho más legible que usar range(len()).

14.7.3) Iniciar enumerate() en un número diferente

Por defecto, enumerate() empieza a contar desde 0. Puedes especificar un número inicial distinto con el parámetro start:

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
# Empezar a contar en 1 en lugar de 0
for position, student in enumerate(students, start=1):
    print(f"Position {position}: {student}")
 
# Output:
# Position 1: Alice
# Position 2: Bob
# Position 3: Charlie
# Position 4: Diana

Esto es útil cuando quieres mostrar numeración amigable para humanos (empezando en 1) en lugar de indexación amigable para programadores (empezando en 0).

Ejemplos prácticos con enumerate()

Aquí tienes un ejemplo práctico que muestra un menú numerado:

python
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
 
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
    print(f"{number}. {item}")
 
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit

14.7.4) Modificar listas con enumerate()

Puedes usar enumerate() cuando necesitas modificar elementos de una lista según su posición:

python
# Añadir un bonus basado en la posición a las puntuaciones
scores = [85, 92, 78, 95, 88]
 
for index, score in enumerate(scores):
    # El primer estudiante obtiene 5 puntos extra, el segundo obtiene 4, etc.
    bonus = 5 - index
    if bonus > 0:
        scores[index] = score + bonus
 
print(scores)  # Output: [90, 96, 81, 97, 89]

14.8) Patrones comunes con listas: buscar, filtrar y agregar datos

14.8.1) Buscar elementos en listas

Una de las tareas más comunes es comprobar si una lista contiene un elemento específico. El operador in (que aprendimos en el Capítulo 7) lo hace sencillo:

python
students = ["Alice", "Bob", "Charlie", "Diana"]
 
# Comprobar si un estudiante está en la lista
if "Charlie" in students:
    print("Charlie is enrolled")  # Output: Charlie is enrolled
 
if "Eve" not in students:
    print("Eve is not enrolled")  # Output: Eve is not enrolled

Para encontrar la posición de un elemento, usa el método index() (cubierto en la sección 14.3.9), pero recuerda comprobar primero si el elemento existe:

python
scores = [85, 92, 78, 95, 88]
target_score = 95
 
if target_score in scores:
    position = scores.index(target_score)
    print(f"Score {target_score} found at index {position}")
    # Output: Score 95 found at index 3
else:
    print(f"Score {target_score} not found")

14.8.2) Encontrar los valores máximo y mínimo

Las funciones integradas max() y min() de Python funcionan con listas:

python
scores = [85, 92, 78, 95, 88, 91, 76]
 
highest_score = max(scores)
lowest_score = min(scores)
 
print(f"Highest score: {highest_score}")  # Output: Highest score: 95
print(f"Lowest score: {lowest_score}")    # Output: Lowest score: 76

14.8.3) Calcular agregados: suma, promedio y conteo

Calcular totales y promedios es una operación fundamental con listas:

python
scores = [85, 92, 78, 95, 88, 91, 76, 89]
 
# Calcular total y promedio
total = sum(scores)
count = len(scores)
average = total / count
 
print(f"Total: {total}")        # Output: Total: 694
print(f"Count: {count}")        # Output: Count: 8
print(f"Average: {average:.2f}")  # Output: Average: 86.75

Aquí tienes un ejemplo práctico calculando el total de un carrito de compra:

python
cart_items = ["Milk", "Bread", "Eggs", "Butter", "Cheese"]
prices = [3.99, 2.49, 4.99, 5.49, 6.99]
 
# Calcular el coste total
total_cost = sum(prices)
item_count = len(cart_items)
 
print(f"Items in cart: {item_count}")
print(f"Total cost: ${total_cost:.2f}")
 
# Output:
# Items in cart: 5
# Total cost: $23.95

14.9) Mutabilidad de listas y truthiness en condiciones

14.9.1) Comprender la mutabilidad de listas en la práctica

A lo largo de este capítulo hemos visto que las listas son mutables: se pueden cambiar después de crearlas. Esta mutabilidad es lo que hace que las listas sean tan potentes para almacenar y manipular colecciones de datos. Consolidemos nuestra comprensión con un ejemplo completo:

python
# Empezar con una lista de tareas vacía
tasks = []
print(f"Initial tasks: {tasks}")  # Output: Initial tasks: []
 
# Añadir tareas
tasks.append("Write code")
tasks.append("Test code")
tasks.append("Deploy code")
print(f"After adding: {tasks}")
# Output: After adding: ['Write code', 'Test code', 'Deploy code']
 
# Insertar una tarea urgente al principio
tasks.insert(0, "Review requirements")
print(f"After inserting: {tasks}")
# Output: After inserting: ['Review requirements', 'Write code', 'Test code', 'Deploy code']
 
# Completar y eliminar la primera tarea
completed = tasks.pop(0)
print(f"Completed: {completed}")  # Output: Completed: Review requirements
print(f"Remaining: {tasks}")
# Output: Remaining: ['Write code', 'Test code', 'Deploy code']
 
# Modificar una tarea
tasks[1] = "Test code thoroughly"
print(f"After modifying: {tasks}")
# Output: After modifying: ['Write code', 'Test code thoroughly', 'Deploy code']

14.9.2) Mutabilidad vs inmutabilidad: listas vs cadenas

Es importante entender la diferencia entre listas mutables y cadenas inmutables. Con las cadenas, las operaciones crean nuevas cadenas en lugar de modificar el original:

python
# Las cadenas son inmutables
text = "hello"
text.upper()  # Crea una nueva cadena, no cambia el original
print(text)  # Output: hello (unchanged)
 
# Para "cambiar" una cadena, debes reasignar
text = text.upper()
print(text)  # Output: HELLO
 
# Las listas son mutables
numbers = [1, 2, 3]
numbers.append(4)  # Modifica la lista en el sitio
print(numbers)  # Output: [1, 2, 3, 4] (changed)

Esta diferencia afecta a cómo trabajas con estos tipos:

python
# Las operaciones con cadenas requieren reasignación
name = "alice"
name = name.capitalize()  # Debes reasignar para ver el cambio
print(name)  # Output: Alice
 
# Las operaciones con listas modifican en el sitio
scores = [85, 92, 78]
scores.append(95)  # No se necesita reasignación
print(scores)  # Output: [85, 92, 78, 95]

14.9.3) Usar listas en contextos booleanos

Las listas tienen truthiness: una lista vacía se considera False, y cualquier lista no vacía se considera True. Esto es útil en sentencias condicionales:

python
# Una lista vacía es falsy
empty_cart = []
if empty_cart:
    print("Cart has items")
else:
    print("Cart is empty")  # Output: Cart is empty
 
# Una lista no vacía es truthy
cart_with_items = ["Milk", "Bread"]
if cart_with_items:
    print("Cart has items")  # Output: Cart has items

Este patrón se usa comúnmente para comprobar si una lista tiene elementos antes de procesarla:

python
students = ["Alice", "Bob", "Charlie"]
 
if students:
    print(f"We have {len(students)} students")
    for student in students:
        print(f"  - {student}")
else:
    print("No students enrolled")
 
# Output:
# We have 3 students
#   - Alice
#   - Bob
#   - Charlie

14.9.4) Patrón práctico: procesar hasta vaciar

La truthiness de las listas permite un patrón útil para procesar elementos hasta que una lista queda vacía:

python
# Procesar tareas hasta que no quede ninguna
tasks = ["Task 1", "Task 2", "Task 3"]
 
while tasks:  # Continuar mientras la lista no esté vacía
    current_task = tasks.pop(0)
    print(f"Processing: {current_task}")
 
print("All tasks completed!")
 
# Output:
# Processing: Task 1
# Processing: Task 2
# Processing: Task 3
# All tasks completed!

14.9.5) Comprobar listas vacías: explícito vs implícito

Hay dos maneras de comprobar si una lista está vacía:

python
items = []
 
# Comprobación implícita (Pythonic)
if not items:
    print("List is empty")  # Output: List is empty
 
# Comprobación explícita (también válida)
if len(items) == 0:
    print("List is empty")  # Output: List is empty

La comprobación implícita (if not items:) generalmente se prefiere en Python porque es más concisa y funciona con cualquier tipo de colección. Sin embargo, ambos enfoques son correctos y verás ambos en código real.

14.9.6) Mutabilidad y comportamiento de funciones

Cuando pasas una lista a una función (que exploraremos en detalle en la Parte V), la función recibe una referencia al mismo objeto lista. Esto significa que la función puede modificar la lista original:

python
def add_item(shopping_list, item):
    shopping_list.append(item)
    print(f"Added {item}")
 
# Se modifica la lista original
cart = ["Milk", "Bread"]
print(f"Before: {cart}")  # Output: Before: ['Milk', 'Bread']
 
add_item(cart, "Eggs")     # Output: Added Eggs
print(f"After: {cart}")    # Output: After: ['Milk', 'Bread', 'Eggs']

Este comportamiento es diferente de tipos inmutables como las cadenas y los números, donde el valor original no puede ser cambiado por una función. Comprender esta distinción es crucial para escribir programas correctos.


Las listas son una de las estructuras de datos más fundamentales y versátiles de Python. Proporcionan una colección ordenada y mutable que puede crecer y reducirse según sea necesario, lo que las hace perfectas para almacenar y procesar secuencias de datos relacionados. Has aprendido cómo crear listas, acceder a sus elementos mediante indexación y slicing, modificarlas con varios métodos, iterar sobre ellas de forma eficiente y entender su naturaleza mutable.

Los patrones que hemos explorado —buscar, filtrar, agregar y transformar datos— forman la base para trabajar con colecciones en Python. A medida que sigas aprendiendo, descubrirás formas aún más potentes de trabajar con listas, incluidas las comprensiones de listas (Capítulo 35) y técnicas avanzadas de iteración (Capítulos 36-37). Pero los fundamentos que has dominado en este capítulo te servirán bien a lo largo de tu trayectoria de programación en Python.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai