15. Tuplas y rangos: secuencias inmutables simples
En el Capítulo 14, exploramos las listas(list)—el tipo de secuencia mutable y versátil de Python. Ahora examinaremos otros dos tipos de secuencia importantes: tuplas(tuple) y rangos(range). Mientras que las listas destacan para almacenar colecciones que cambian con el tiempo, las tuplas proporcionan secuencias inmutables que protegen los datos de la modificación, y los rangos ofrecen formas eficientes en memoria de representar secuencias de números.
Comprender cuándo usar cada tipo de secuencia hará que tus programas sean más eficientes, más seguros y más claros en su intención. Al final de este capítulo, sabrás cómo trabajar con tuplas y rangos de forma efectiva, y entenderás las operaciones comunes que funcionan en todos los tipos de secuencia de Python.
15.1) Crear y usar tuplas (la importancia de la coma)
Una tupla(tuple) es una secuencia ordenada e inmutable de elementos. Al igual que las listas, las tuplas pueden contener cualquier tipo de dato y mantienen el orden de los elementos. Sin embargo, a diferencia de las listas, una vez que creas una tupla, no puedes modificar su contenido.
Crear tuplas con paréntesis
La forma más común de crear una tupla es encerrando valores separados por comas entre paréntesis:
# Una tupla de calificaciones de exámenes de estudiantes
scores = (85, 92, 78, 95)
print(scores) # Output: (85, 92, 78, 95)
print(type(scores)) # Output: <class 'tuple'>
# Una tupla de tipos de datos mixtos
student_info = ("Alice", 20, "Computer Science", 3.8)
print(student_info) # Output: ('Alice', 20, 'Computer Science', 3.8)
# Una tupla vacía
empty = ()
print(empty) # Output: ()
print(len(empty)) # Output: 0Las tuplas usan paréntesis () para su sintaxis literal, mientras que las listas usan corchetes []. Esta distinción visual te ayuda a reconocer inmediatamente con qué tipo estás trabajando.
La coma crea la tupla, no los paréntesis
Aquí hay un detalle crucial que sorprende a muchos principiantes: la coma es lo que realmente crea una tupla, no los paréntesis. Los paréntesis a menudo son opcionales y sirven principalmente para hacer la tupla más visible o para agruparla en expresiones.
# Todas estas crean la misma tupla
coordinates_1 = (10, 20)
coordinates_2 = 10, 20 # ¡No se necesitan paréntesis!
print(coordinates_1) # Output: (10, 20)
print(coordinates_2) # Output: (10, 20)
print(coordinates_1 == coordinates_2) # Output: True
# Lo que importa es la coma
x = (42) # Esto es solo el entero 42 entre paréntesis
y = (42,) # Esto es una tupla que contiene un elemento
print(type(x)) # Output: <class 'int'>
print(type(y)) # Output: <class 'tuple'>
print(y) # Output: (42,)Los paréntesis en (42) son solo paréntesis de agrupación, como en expresiones matemáticas. Para crear una tupla de un solo elemento, debes incluir una coma final: (42,). Esta coma le indica a Python que quieres una tupla, no solo una expresión agrupada.
Cuándo se requieren paréntesis
Aunque la coma crea la tupla, los paréntesis se vuelven necesarios en ciertas situaciones para evitar ambigüedades:
# Sin paréntesis, esto sería confuso
def get_dimensions():
return 1920, 1080 # Devuelve una tupla
width, height = get_dimensions()
print(f"Screen: {width}x{height}") # Output: Screen: 1920x1080
# Se necesitan paréntesis al pasar tuplas como argumentos de función
print((1, 2, 3)) # Output: (1, 2, 3)
# Sin paréntesis, Python vería tres argumentos separados
# Se necesitan paréntesis en expresiones complejas
result = (10, 20) + (30, 40) # Concatenación de tuplas
print(result) # Output: (10, 20, 30, 40)Crear tuplas de un solo elemento
El requisito de la coma final para tuplas de un solo elemento a menudo toma por sorpresa a los principiantes:
# Error común: olvidar la coma
not_a_tuple = ("Python")
print(type(not_a_tuple)) # Output: <class 'str'>
print(not_a_tuple) # Output: Python
# Correcto: incluye la coma final
is_a_tuple = ("Python",)
print(type(is_a_tuple)) # Output: <class 'tuple'>
print(is_a_tuple) # Output: ('Python',)
# La coma funciona incluso sin paréntesis
also_a_tuple = "Python",
print(type(also_a_tuple)) # Output: <class 'tuple'>
print(also_a_tuple) # Output: ('Python',)¿Por qué Python requiere esta sintaxis aparentemente incómoda? Porque los paréntesis ya tienen otro significado en Python: agrupan expresiones. Sin la coma, Python no tiene forma de distinguir entre (42) como un número agrupado y (42) como una tupla.
Acceder a elementos de una tupla
Las tuplas admiten las mismas operaciones de indexación e slicing que las listas:
# Tupla de información del estudiante
student = ("Bob", 22, "Physics", 3.6)
# Acceder a elementos individuales (indexado desde cero)
name = student[0]
age = student[1]
major = student[2]
gpa = student[3]
print(f"{name} is {age} years old") # Output: Bob is 22 years old
print(f"Major: {major}, GPA: {gpa}") # Output: Major: Physics, GPA: 3.6
# La indexación negativa también funciona
last_item = student[-1]
print(f"Last item: {last_item}") # Output: Last item: 3.6
# El slicing extrae una tupla nueva
first_two = student[:2]
print(first_two) # Output: ('Bob', 22)
print(type(first_two)) # Output: <class 'tuple'>Cada técnica de indexación y slicing que aprendiste con listas en el Capítulo 14 funciona de forma idéntica con tuplas. La diferencia clave es que las tuplas no se pueden modificar después de crearlas.
15.2) Empaquetado y desempaquetado de tuplas
Una de las características más potentes y elegantes de las tuplas es su capacidad de empaquetar varios valores juntos y desempaquetarlos en variables separadas. Esta característica hace que el código Python sea notablemente conciso y legible.
Empaquetado de tuplas
El empaquetado de tuplas(tuple packing) ocurre cuando creas una tupla colocando varios valores juntos, separados por comas:
# Empaquetar valores en una tupla
coordinates = 10, 20, 30
print(coordinates) # Output: (10, 20, 30)
# Empaquetar diferentes tipos
user_data = "Alice", 25, "alice@example.com"
print(user_data) # Output: ('Alice', 25, 'alice@example.com')
# Empaquetar valores devueltos por una función
def get_statistics(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count
return total, count, average # Empaqueta tres valores en una tupla
stats = get_statistics([85, 90, 78, 92, 88])
print(stats) # Output: (433, 5, 86.6)Cuando una función devuelve múltiples valores separados por comas, Python los empaqueta automáticamente en una tupla. Por eso las funciones pueden parecer que devuelven múltiples valores: en realidad están devolviendo una única tupla que contiene esos valores.
Desempaquetado de tuplas
El desempaquetado de tuplas(tuple unpacking) es el proceso inverso: extraer valores de una tupla en variables separadas:
# Desempaquetado básico
point = (100, 200)
x, y = point
print(f"x = {x}, y = {y}") # Output: x = 100, y = 200
# El desempaquetado funciona con cualquier secuencia, no solo con tuplas
name, age, email = ["Bob", 30, "bob@example.com"]
print(f"{name} is {age} years old") # Output: Bob is 30 years old
# Desempaquetar directamente los valores devueltos por una función
total, count, average = get_statistics([95, 88, 92, 85])
print(f"Average of {count} scores: {average}") # Output: Average of 4 scores: 90.0La cantidad de variables en el lado izquierdo debe coincidir con el número de elementos en la secuencia. Si no coinciden, Python lanza un ValueError:
# Esto causará un error
coordinates = (10, 20, 30)
# x, y = coordinates # ValueError: too many values to unpack (expected 2)
# Esto también causará un error
point = (5, 10)
# x, y, z = point # ValueError: not enough values to unpack (expected 3, got 2)Intercambiar variables con desempaquetado de tuplas
El desempaquetado de tuplas permite una forma elegante de intercambiar valores de variables sin necesitar una variable temporal:
# Intercambio tradicional usando una variable temporal
a = 10
b = 20
temp = a
a = b
b = temp
print(f"a = {a}, b = {b}") # Output: a = 20, b = 10
# El intercambio elegante de Python usando desempaquetado de tuplas
x = 100
y = 200
x, y = y, x # ¡Intercambio en una línea!
print(f"x = {x}, y = {y}") # Output: x = 200, y = 100
# Intercambiar más de dos variables
first = "A"
second = "B"
third = "C"
first, second, third = third, first, second
print(first, second, third) # Output: C A B¿Cómo funciona esto? Python evalúa primero el lado derecho, creando una tupla (y, x), y luego la desempaqueta en las variables del lado izquierdo. Esto sucede en un solo paso, así que no se necesita una variable temporal.
Desempaquetado extendido con el operador estrella
Python proporciona desempaquetado extendido(extended unpacking) usando el operador * para capturar múltiples elementos:
# Desempaquetado con una variable de "resto"
scores = (95, 88, 92, 85, 90, 87)
first, second, *rest = scores
print(f"Top two: {first}, {second}") # Output: Top two: 95, 88
print(f"Others: {rest}") # Output: Others: [92, 85, 90, 87]
print(type(rest)) # Output: <class 'list'>
# La estrella puede aparecer en cualquier lugar
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(f"First: {first}") # Output: First: 1
print(f"Middle: {middle}") # Output: Middle: [2, 3, 4]
print(f"Last: {last}") # Output: Last: 5
# Capturar el inicio
*beginning, second_last, last = numbers
print(f"Beginning: {beginning}") # Output: Beginning: [1, 2, 3]
print(f"Last two: {second_last}, {last}") # Output: Last two: 4, 5Observa que la variable con estrella siempre captura los elementos como una lista(list), incluso cuando se desempaqueta desde una tupla. Si no hay elementos que capturar, la variable con estrella se convierte en una lista vacía:
# Cuando no hay nada que capturar
a, b, *rest = (10, 20)
print(rest) # Output: []
# Solo se permite una estrella por desempaquetado
# first, *middle, *end = (1, 2, 3, 4) # SyntaxError: multiple starred expressionsIgnorar valores con guion bajo
A veces solo necesitas ciertos valores de una tupla. Por convención, los programadores de Python usan el guion bajo _ como nombre de variable para indicar valores que quieren ignorar:
# Parsear una cadena de fecha
date_string = "2024-03-15"
year, month, day = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Si solo nos importa el mes
_, month, _ = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Con desempaquetado extendido
data = ("Alice", 25, "Engineer", "New York", "alice@example.com")
name, age, *_, email = data
print(f"{name} ({age}): {email}") # Output: Alice (25): alice@example.comEl guion bajo es solo un nombre de variable normal, pero usarlo señala a otros programadores (y a ti mismo) que estás ignorando esos valores de forma intencional.
Ejemplos prácticos de empaquetado y desempaquetado
# Devolver múltiples valores de cálculos
def calculate_rectangle_properties(width, height):
"""Calculate area and perimeter of a rectangle."""
area = width * height
perimeter = 2 * (width + height)
return area, perimeter # Packing
# Desempaquetar los resultados
rect_area, rect_perimeter = calculate_rectangle_properties(5, 3)
print(f"Area: {rect_area}, Perimeter: {rect_perimeter}") # Output: Area: 15, Perimeter: 16
# Iterar con desempaquetado
students = [
("Alice", 85),
("Bob", 92),
("Carol", 78)
]
for name, score in students: # Desempaquetado en el bucle
print(f"{name}: {score}")
# Output:
# Alice: 85
# Bob: 92
# Carol: 78El empaquetado y desempaquetado de tuplas hacen que el código Python sea más legible y expresivo. En lugar de acceder a elementos de una tupla por índice (student[0], student[1]), puedes desempaquetarlos en variables con nombres significativos.
15.3) Las tuplas son inmutables: cuándo eso es útil
La característica definitoria de las tuplas es su inmutabilidad(immutability): una vez creada, el contenido de una tupla no se puede cambiar. No puedes añadir, eliminar ni modificar elementos. Esta inmutabilidad puede parecer una limitación, pero ofrece beneficios importantes.
Qué significa la inmutabilidad en la práctica
# Crear una tupla
coordinates = (10, 20, 30)
print(coordinates) # Output: (10, 20, 30)
# Intentar modificarla provoca un error
# coordinates[0] = 15 # TypeError: 'tuple' object does not support item assignment
# Intentar añadir elementos provoca un error
# coordinates.append(40) # AttributeError: 'tuple' object has no attribute 'append'
# Intentar eliminar elementos provoca un error
# del coordinates[1] # TypeError: 'tuple' object doesn't support item deletionCuando Python dice que las tuplas no admiten asignación de elementos, significa que no puedes cambiar lo que está almacenado en ninguna posición de la tupla. La estructura de la tupla queda fija en el momento de la creación.
Comparar listas mutables y tuplas inmutables
# Las listas son mutables: puedes cambiarlas
shopping_list = ["milk", "bread", "eggs"]
shopping_list[1] = "butter" # Modificar un elemento
shopping_list.append("cheese") # Añadir un elemento
print(shopping_list) # Output: ['milk', 'butter', 'eggs', 'cheese']
# Las tuplas son inmutables: no puedes cambiarlas
product_dimensions = (10, 20, 5) # width, height, depth in cm
# product_dimensions[0] = 12 # TypeError: cannot modify
# product_dimensions.append(3) # AttributeError: no append method
# Para "cambiar" una tupla, debes crear una nueva
new_dimensions = (12, 20, 5) # Crear una tupla completamente nueva
print(new_dimensions) # Output: (12, 20, 5)Por qué la inmutabilidad es útil
La inmutabilidad ofrece varios beneficios prácticos:
1. Integridad y seguridad de los datos
Cuando pasas una tupla a una función, sabes que la función no puede modificar tus datos por accidente:
def calculate_distance(point1, point2):
"""Calculate distance between two 2D points."""
x1, y1 = point1
x2, y2 = point2
dx = x2 - x1
dy = y2 - y1
# Incluso si quisiéramos, no podemos modificar las tuplas de entrada
return (dx**2 + dy**2) ** 0.5
start = (0, 0)
end = (3, 4)
distance = calculate_distance(start, end)
print(f"Distance: {distance}") # Output: Distance: 5.0
print(f"Start point unchanged: {start}") # Output: Start point unchanged: (0, 0)Con listas, tendrías que preocuparte por si una función podría modificar tus datos. Con tuplas, tienes la garantía de que no lo hará.
2. Usar tuplas como claves de diccionario
Como exploraremos más en el Capítulo 17, las claves de diccionario deben ser hasheables(hashable): deben tener un valor hash que nunca cambie. Los objetos inmutables como las tuplas pueden ser claves de diccionario; los objetos mutables como las listas no pueden:
# Las tuplas pueden ser claves de diccionario
locations = {
(0, 0): "Origin",
(10, 20): "Point A",
(30, 40): "Point B"
}
print(locations[(10, 20)]) # Output: Point A
# Las listas no pueden ser claves de diccionario
# locations_bad = {
# [0, 0]: "Origin" # TypeError: unhashable type: 'list'
# }3. Señalar la intención
Usar una tupla en lugar de una lista comunica a otros programadores (y a ti mismo) que estos datos no deberían cambiar:
# Valores de color RGB: estos nunca deberían cambiar
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Parámetros de conexión a base de datos: configuración fija
DB_CONFIG = ("localhost", 5432, "myapp", "production")
# Coordenadas geográficas: una ubicación no cambia
EIFFEL_TOWER = (48.8584, 2.2945) # latitude, longitudeCuando ves una tupla en el código, sabes inmediatamente que estos datos están pensados para permanecer constantes. Cuando ves una lista, sabes que podría modificarse.
4. Beneficios de rendimiento
Como las tuplas son inmutables, Python puede optimizarlas de maneras que no puede optimizar las listas. Aprenderemos sobre el módulo sys en el Capítulo 27, pero por ahora, solo debes saber que sys.getsizeof() nos dice cuánta memoria usa un objeto:
import sys
# Las tuplas usan menos memoria que las listas equivalentes
tuple_data = (1, 2, 3, 4, 5)
list_data = [1, 2, 3, 4, 5]
print(f"Tuple size: {sys.getsizeof(tuple_data)} bytes") # Output: Tuple size: 80 bytes (may vary by Python version)
print(f"List size: {sys.getsizeof(list_data)} bytes") # Output: List size: 104 bytes (may vary by Python version)
# Crear tuplas es más rápido
import timeit
tuple_time = timeit.timeit("(1, 2, 3, 4, 5)", number=1000000)
list_time = timeit.timeit("[1, 2, 3, 4, 5]", number=1000000)
print(f"Tuple creation: {tuple_time:.4f} seconds")
print(f"List creation: {list_time:.4f} seconds")
# Example output: Tuple creation: 0.0055 seconds, List creation: 0.0292 seconds15.4) La trampa de la inmutabilidad: cuando las tuplas contienen elementos mutables
Aunque las tuplas en sí son inmutables, pueden contener objetos mutables como listas o diccionarios. Esto crea una distinción sutil pero importante: la estructura de la tupla es fija, pero el contenido de los objetos mutables dentro de ella todavía puede cambiar.
Comprender la distinción
# Una tupla que contiene una lista
student_data = ("Alice", 20, [85, 90, 78]) # name, age, scores
print(student_data) # Output: ('Alice', 20, [85, 90, 78])
# No podemos reasignar elementos de la tupla
# student_data[0] = "Bob" # TypeError: 'tuple' object does not support item assignment
# Pero SÍ podemos modificar la lista dentro de la tupla
student_data[2].append(92) # Añadir una nueva nota
print(student_data) # Output: ('Alice', 20, [85, 90, 78, 92])
student_data[2][0] = 88 # Modificar una nota existente
print(student_data) # Output: ('Alice', 20, [88, 90, 78, 92])¿Qué está pasando aquí? La tupla almacena tres referencias: una a la cadena "Alice", una al entero 20 y una a un objeto lista. La estructura de la tupla—qué objetos referencia—no puede cambiar. Pero el objeto lista en sí es mutable, así que su contenido puede cambiar.
Visualizar la diferencia
# La estructura de la tupla es fija
data = ("Python", [1, 2, 3])
# Esto intenta cambiar lo que referencia la tupla: NO PERMITIDO
# data[1] = [4, 5, 6] # TypeError
# Esto modifica la lista que referencia la tupla: PERMITIDO
data[1].append(4)
print(data) # Output: ('Python', [1, 2, 3, 4])
# La tupla sigue referenciando el mismo objeto lista
# Solo cambió el contenido de la lista, no qué lista señala la tuplaPiénsalo así: una tupla es como una fila de cajas, y cada caja contiene una referencia a un objeto. Las cajas en sí están bloqueadas en su lugar (inmutables), pero si una caja contiene una referencia a un objeto mutable, ese objeto todavía puede cambiar.
Tuplas con diccionarios
El mismo principio se aplica a los diccionarios dentro de tuplas:
# Tupla que contiene un diccionario
user_profile = ("alice", {"email": "alice@example.com", "age": 25})
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 25})
# No se puede cambiar qué diccionario referencia la tupla
# user_profile[1] = {"email": "newemail@example.com"} # TypeError
# Pero SÍ se puede modificar el diccionario en sí
user_profile[1]["age"] = 26
user_profile[1]["city"] = "New York"
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 26, 'city': 'New York'})Por qué esto importa para claves de diccionario
Las tuplas se pueden usar como claves de diccionario solo si todos sus elementos son hasheables. Aunque las tuplas en sí son inmutables, una tupla que contiene objetos mutables (como listas) no es hasheable en absoluto y, por lo tanto, no puede usarse como clave de diccionario.
# Esto funciona pero es peligroso
tuple_with_list = ("key", [1, 2, 3])
# data = {tuple_with_list: "value"} # TypeError: unhashable type: 'list'Usa solo tuplas que contengan objetos totalmente inmutables (cadenas, números, frozensets, otras tuplas) como claves de diccionario.
Crear tuplas verdaderamente inmutables
Si necesitas una tupla que sea completamente inmutable, asegúrate de que todo su contenido también sea inmutable:
# Tupla totalmente inmutable: solo tipos inmutables
point_3d = (10, 20, 30) # Todos enteros
rgb_color = (255, 128, 0) # Todos enteros
coordinates = ((10, 20), (30, 40)) # Tupla de tuplas
# Estas son seguras para usarse como claves de diccionario
color_names = {
(255, 0, 0): "Red",
(0, 255, 0): "Green",
(0, 0, 255): "Blue"
}
# Las tuplas anidadas permanecen inmutables
nested = ((1, 2), (3, 4))
# nested[0][0] = 5 # TypeError: 'tuple' object does not support item assignmentCuando el contenido mutable es intencional
A veces en realidad quieres una tupla con contenido mutable; por ejemplo, cuando tienes una estructura de registro fija pero un campo necesita cambiar:
# Registro de estudiante con identidad fija pero calificaciones cambiantes
def create_student(name, student_id):
"""Create a student record with empty grade list."""
return (name, student_id, []) # name and ID fixed, grades can change
student = create_student("Alice", "S12345")
print(student) # Output: ('Alice', 'S12345', [])
# La identidad del estudiante es fija
print(f"Student: {student[0]} (ID: {student[1]})") # Output: Student: Alice (ID: S12345)
# Pero podemos añadir calificaciones a medida que se obtienen
student[2].append(85)
student[2].append(92)
student[2].append(78)
print(f"Grades: {student[2]}") # Output: Grades: [85, 92, 78]
# La estructura de la tupla protege el nombre y el ID de cambios accidentales
# mientras permite que la lista de calificaciones crezcaEste patrón es útil cuando quieres proteger algunos datos mientras permites que otros datos cambien. Solo ten en cuenta la distinción entre la inmutabilidad de la tupla y la mutabilidad de su contenido.
15.5) Cuándo usar tuplas en lugar de listas
Elegir entre tuplas y listas es una decisión de diseño importante. Aunque ambas son secuencias, cumplen propósitos diferentes y comunican intenciones distintas.
Usa tuplas para datos fijos y heterogéneos
Las tuplas funcionan mejor cuando tienes un número fijo de elementos que representan una única entidad lógica, a menudo con tipos diferentes:
# Registro de estudiante: nombre, edad, carrera, GPA
student = ("Alice", 20, "Computer Science", 3.8)
# Coordenadas geográficas: latitud, longitud
location = (40.7128, -74.0060) # New York City
# Color RGB: rojo, verde, azul
color = (255, 128, 0)
# Conexión a base de datos: host, puerto, base de datos, usuario
db_connection = ("localhost", 5432, "myapp", "admin")
# Fecha: año, mes, día
date = (2024, 3, 15)Cada tupla representa un "registro" completo donde la posición de cada elemento tiene un significado específico. El primer elemento siempre es el nombre, el segundo siempre es la edad, y así sucesivamente.
Usa listas para colecciones homogéneas
Las listas funcionan mejor cuando tienes un número variable de elementos similares que podrías añadir, eliminar o reordenar:
# Lista de compras: elementos del mismo tipo (cadenas)
shopping_list = ["milk", "bread", "eggs", "butter"]
shopping_list.append("cheese") # Añadir más elementos según sea necesario
shopping_list.remove("bread") # Eliminar elementos
# Calificaciones: elementos del mismo tipo (números)
test_scores = [85, 92, 78, 95, 88]
test_scores.append(90) # Añadir una nueva calificación
test_scores.sort() # Reordenar calificaciones
# Nombres de usuario: elementos del mismo tipo (cadenas)
active_users = ["alice", "bob", "carol"]
active_users.extend(["dave", "eve"]) # Añadir múltiples usuariosLas listas son para colecciones donde el número de elementos puede cambiar y donde cada elemento cumple el mismo rol.
Tuplas para valores de retorno de funciones
Cuando una función devuelve múltiples valores relacionados, las tuplas son la opción natural:
def get_user_info(user_id):
"""Retrieve user information from database."""
# Simular una búsqueda en base de datos
return "Alice", "alice@example.com", 25, "New York"
# Desempaquetar la tupla devuelta
name, email, age, city = get_user_info(101)
print(f"{name} from {city}") # Output: Alice from New York
def calculate_statistics(numbers):
"""Calculate min, max, and average of numbers."""
if not numbers:
return None, None, None
minimum = min(numbers)
maximum = max(numbers)
average = sum(numbers) / len(numbers)
return minimum, maximum, average
# Desempaquetar los resultados
min_val, max_val, avg_val = calculate_statistics([85, 92, 78, 95, 88])
print(f"Range: {min_val} to {max_val}, Average: {avg_val}")
# Output: Range: 78 to 95, Average: 87.6Devolver tuplas deja claro que estos valores están relacionados y deben considerarse en conjunto.
Tuplas para claves de diccionario
Cuando necesitas claves compuestas en un diccionario, las tuplas son esenciales:
# Calificaciones de estudiantes por curso y semestre
grades = {
("CS101", "Fall2023"): 85,
("CS101", "Spring2024"): 90,
("MATH201", "Fall2023"): 88,
("MATH201", "Spring2024"): 92
}
# Buscar una calificación específica
course = "CS101"
semester = "Spring2024"
grade = grades[(course, semester)]
print(f"Grade in {course} ({semester}): {grade}") # Output: Grade in CS101 (Spring2024): 90
# Coordenadas de una cuadrícula como claves de diccionario
grid = {
(0, 0): "Start",
(5, 3): "Obstacle",
(10, 10): "Goal"
}
position = (5, 3)
if position in grid:
print(f"At {position}: {grid[position]}") # Output: At (5, 3): ObstacleLas listas no pueden ser claves de diccionario porque son mutables, pero las tuplas sí pueden.
Tuplas para configuración inmutable
Cuando tienes datos de configuración que nunca deberían cambiar, las tuplas señalan esa intención:
# Configuración de la aplicación que debería permanecer constante
APP_CONFIG = (
"MyApp", # Nombre de la aplicación
"1.0.0", # Versión
"production", # Entorno
True, # Modo debug
8080 # Puerto
)
# Paleta de colores para UI: estos colores son fijos
COLOR_PALETTE = (
(255, 0, 0), # Rojo primario
(0, 128, 255), # Azul primario
(255, 255, 255), # Blanco
(0, 0, 0) # Negro
)
# Endpoints de API: estas URLs no cambian
API_ENDPOINTS = (
"https://api.example.com/users",
"https://api.example.com/products",
"https://api.example.com/orders"
)Guía de decisión
# Usa TUPLAS cuando:
# 1. Los datos representan un solo registro con estructura fija
employee = ("E001", "Alice", "Engineering", 75000)
# 2. Devuelves múltiples valores desde una función
def divide_with_remainder(a, b):
return a // b, a % b
# 3. Necesitas usarlo como claves de diccionario
cache = {(5, 10): 50, (3, 7): 21}
# 4. Los datos no deberían modificarse
SCREEN_RESOLUTION = (1920, 1080)
# Usa LISTAS cuando:
# 1. Colección de elementos similares que podría cambiar
tasks = ["Write code", "Test code", "Deploy code"]
tasks.append("Document code")
# 2. Necesitas añadir, eliminar o reordenar elementos
scores = [85, 90, 78]
scores.sort()
scores.append(92)
# 3. Todos los elementos cumplen el mismo propósito
usernames = ["alice", "bob", "carol"]
# 4. El tamaño de la colección no se conoce de antemano
results = []
for i in range(10):
results.append(i * 2)15.6) Comprender los objetos range en profundidad
Ahora que entendemos cuándo usar tuplas frente a listas, exploremos el tercer tipo de secuencia inmutable de Python: los rangos(range). El tipo range(range) representa una secuencia inmutable de números. A diferencia de las listas y tuplas que almacenan todos sus elementos en memoria, los objetos range generan números bajo demanda, lo que los hace extremadamente eficientes en memoria para representar secuencias grandes.
Crear objetos range
La función range() crea objetos range con tres formas:
# Un argumento: range(stop)
# Genera números desde 0 hasta (pero sin incluir) stop
numbers = range(5)
print(list(numbers)) # Output: [0, 1, 2, 3, 4]
# Dos argumentos: range(start, stop)
# Genera números desde start hasta (pero sin incluir) stop
numbers = range(2, 7)
print(list(numbers)) # Output: [2, 3, 4, 5, 6]
# Tres argumentos: range(start, stop, step)
# Genera números desde start hasta stop, incrementando en step
numbers = range(0, 10, 2)
print(list(numbers)) # Output: [0, 2, 4, 6, 8]
# step negativo para contar hacia atrás
numbers = range(10, 0, -1)
print(list(numbers)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]Ten en cuenta que convertimos los rangos a listas con list() para ver su contenido. Un objeto range por sí solo no muestra todos sus valores cuando se imprime:
r = range(5)
print(r) # Output: range(0, 5)
print(type(r)) # Output: <class 'range'>Cómo funcionan los objetos range
Los objetos range no almacenan todos sus valores en memoria. En su lugar, calculan cada valor cuando se necesita:
import sys
# Un rango que representa un millón de números
large_range = range(1000000)
print(f"Range size: {sys.getsizeof(large_range)} bytes") # Output: Range size: 48 bytes (may vary by Python version)
# Una lista que contiene un millón de números
large_list = list(range(1000000))
print(f"List size: {sys.getsizeof(large_list)} bytes") # Output: List size: 8000056 bytes (approximately 8MB)
# ¡El rango es diminuto; la lista es enorme!Un objeto range solo almacena tres valores: start, stop y step. Calcula cada número de la secuencia cuando se lo pides. Esto hace que los rangos sean increíblemente eficientes para secuencias grandes.
Usar range en bucles for
Como aprendimos en el Capítulo 12, los rangos se usan con mayor frecuencia con bucles for:
# Contar de 0 a 4
for i in range(5):
print(f"Count: {i}")
# Output:
# Count: 0
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Contar de 1 a 10
for i in range(1, 11):
print(i, end=" ")
print() # Output: 1 2 3 4 5 6 7 8 9 10
# Contar de dos en dos
for i in range(0, 20, 2):
print(i, end=" ")
print() # Output: 0 2 4 6 8 10 12 14 16 18
# Contar hacia atrás
for i in range(5, 0, -1):
print(f"T-minus {i}")
# Output:
# T-minus 5
# T-minus 4
# T-minus 3
# T-minus 2
# T-minus 1Indexación y slicing de objetos range
Los objetos range admiten indexación y slicing igual que otras secuencias:
# Crear un rango
numbers = range(10, 50, 5) # 10, 15, 20, 25, 30, 35, 40, 45
# Indexación
print(numbers[0]) # Output: 10
print(numbers[3]) # Output: 25
print(numbers[-1]) # Output: 45
# El slicing devuelve un nuevo range
subset = numbers[2:5]
print(subset) # Output: range(20, 35, 5)
print(list(subset)) # Output: [20, 25, 30]
# Longitud
print(len(numbers)) # Output: 8Comprobar pertenencia
Puedes comprobar si un número está en un rango usando el operador in:
# Números pares de 0 a 20
evens = range(0, 21, 2)
print(10 in evens) # Output: True
print(15 in evens) # Output: False
print(20 in evens) # Output: True
# Esto es muy eficiente: Python no genera todos los números
# Calcula si el número estaría en la secuencia
large_range = range(0, 1000000, 3)
print(999999 in large_range) # Output: True (instant, no iteration needed)Python puede determinar la pertenencia matemáticamente sin generar todos los números, lo que hace que esta operación sea extremadamente rápida incluso para rangos enormes.
Rangos vacíos e inversos
# Rango vacío: stop es igual a start
empty = range(5, 5)
print(list(empty)) # Output: []
print(len(empty)) # Output: 0
# Rango vacío: imposible alcanzar stop con el step dado
impossible = range(1, 10, -1) # Can't count up with negative step
print(list(impossible)) # Output: []
# Rango inverso
backwards = range(10, 0, -1)
print(list(backwards)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# Inverso con números negativos
negative_range = range(-5, -15, -2)
print(list(negative_range)) # Output: [-5, -7, -9, -11, -13]Cuándo usar range vs listas
# Usa range cuando:
# 1. Necesitas una secuencia de números para iterar
for i in range(100):
# Procesar algo 100 veces
pass
# 2. Necesitas índices para una secuencia
items = ["a", "b", "c", "d"]
for i in range(len(items)):
print(f"Index {i}: {items[i]}")
# 3. La eficiencia en memoria importa con secuencias grandes
# Esto usa memoria mínima
for i in range(1000000):
if i % 100000 == 0:
print(i)
# Usa listas cuando:
# 1. Necesitas almacenar los valores reales
squares = [1, 3, 5, 7, 10]
# 2. Necesitas modificar la secuencia
numbers = list(range(5))
numbers[2] = 100 # Modificar un valor
numbers.append(200) # Añadir un valor
# 3. Necesitas usar la secuencia varias veces con diferentes operaciones
data = list(range(10))
print(sum(data))
print(max(data))
print(sorted(data, reverse=True))Los objetos range son un ejemplo perfecto de la eficiencia de Python. Proporcionan todos los beneficios de una secuencia sin el coste en memoria de almacenar cada elemento.
15.7) Convertir entre listas, tuplas y rangos
Python facilita convertir entre diferentes tipos de secuencia. Comprender estas conversiones te ayuda a elegir el tipo adecuado para cada situación y a transformar datos según sea necesario.
Convertir a listas
La función list() convierte cualquier secuencia en una lista:
# Tupla a lista
student_tuple = ("Alice", 20, "CS")
student_list = list(student_tuple)
print(student_list) # Output: ['Alice', 20, 'CS']
print(type(student_list)) # Output: <class 'list'>
# Ahora podemos modificarla
student_list[1] = 21
student_list.append(3.8)
print(student_list) # Output: ['Alice', 21, 'CS', 3.8]
# Range a lista
numbers = range(5)
numbers_list = list(numbers)
print(numbers_list) # Output: [0, 1, 2, 3, 4]
# Cadena a lista (cada carácter se convierte en un elemento)
text = "Python"
chars = list(text)
print(chars) # Output: ['P', 'y', 't', 'h', 'o', 'n']Convertir a lista es útil cuando necesitas modificar una secuencia o cuando necesitas usar métodos específicos de lista como append(), sort() o remove().
Convertir a tuplas
La función tuple() convierte cualquier secuencia en una tupla:
# Lista a tupla
scores_list = [85, 90, 78, 92]
scores_tuple = tuple(scores_list)
print(scores_tuple) # Output: (85, 90, 78, 92)
print(type(scores_tuple)) # Output: <class 'tuple'>
# Ahora es inmutable
# scores_tuple[0] = 88 # TypeError: 'tuple' object does not support item assignment
# Range a tupla
numbers = range(1, 6)
numbers_tuple = tuple(numbers)
print(numbers_tuple) # Output: (1, 2, 3, 4, 5)
# Cadena a tupla
text = "Hi"
chars_tuple = tuple(text)
print(chars_tuple) # Output: ('H', 'i')Convertir a tupla es útil cuando quieres proteger datos de modificaciones o cuando necesitas usar una secuencia como clave de diccionario.
15.8) Operaciones comunes de secuencias en strings, listas, tuplas y rangos
Los tipos de secuencia de Python—strings(string), listas, tuplas y rangos—comparten muchas operaciones comunes. Comprender estas operaciones compartidas te ayuda a trabajar de forma eficiente con cualquier tipo de secuencia.
Longitud, mínimo y máximo
Todas las secuencias admiten las funciones len(), min() y max():
# Strings
text = "Python"
print(len(text)) # Output: 6
print(min(text)) # Output: P (smallest character by Unicode value)
print(max(text)) # Output: y (largest character by Unicode value)
# Listas
numbers = [45, 12, 78, 23, 56]
print(len(numbers)) # Output: 5
print(min(numbers)) # Output: 12
print(max(numbers)) # Output: 78
# Tuplas
scores = (85, 92, 78, 95, 88)
print(len(scores)) # Output: 5
print(min(scores)) # Output: 78
print(max(scores)) # Output: 95
# Rangos
nums = range(10, 50, 5)
print(len(nums)) # Output: 8
print(min(nums)) # Output: 10
print(max(nums)) # Output: 45Para que min() y max() funcionen, los elementos deben ser comparables. No puedes encontrar el mínimo de una lista que contenga tanto strings como números:
mixed = [1, "hello", 3]
# print(min(mixed)) # TypeError: '<' not supported between instances of 'str' and 'int'Indexación e indexación negativa
Todas las secuencias admiten indexación con índices positivos y negativos:
# Indexación positiva (basada en 0)
text = "Python"
numbers = [10, 20, 30, 40, 50]
coords = (5, 10, 15)
values = range(0, 100, 10)
print(text[0]) # Output: P
print(numbers[2]) # Output: 30
print(coords[1]) # Output: 10
print(values[3]) # Output: 30
# Indexación negativa (desde el final)
print(text[-1]) # Output: n (last character)
print(numbers[-2]) # Output: 40 (second from end)
print(coords[-3]) # Output: 5 (third from end, which is first)
print(values[-1]) # Output: 90 (last value in range)Los índices negativos cuentan desde el final: -1 es el último elemento, -2 es el penúltimo, y así sucesivamente.
Pruebas de pertenencia con in y not in
Todas las secuencias admiten pruebas de pertenencia:
# Strings: comprueba substrings
text = "Python Programming"
print("Python" in text) # Output: True
print("Java" in text) # Output: False
print("gram" in text) # Output: True (substring)
print("PYTHON" not in text) # Output: True (case-sensitive)
# Listas
fruits = ["apple", "banana", "cherry", "date"]
print("banana" in fruits) # Output: True
print("grape" in fruits) # Output: False
print("apple" not in fruits) # Output: False
# Tuplas
coordinates = (10, 20, 30, 40)
print(20 in coordinates) # Output: True
print(25 in coordinates) # Output: False
print(50 not in coordinates) # Output: True
# Rangos: muy eficiente, no se necesita iteración
numbers = range(0, 100, 2) # Even numbers 0 to 98
print(50 in numbers) # Output: True
print(51 in numbers) # Output: False (odd number)
print(100 in numbers) # Output: False (stop is exclusive)Para rangos, Python puede determinar la pertenencia matemáticamente sin comprobar cada elemento, lo que lo hace extremadamente rápido incluso para rangos enormes.
Concatenación y repetición
Strings, listas y tuplas admiten concatenación con + y repetición con *:
# Concatenación con +
text1 = "Hello"
text2 = " World"
print(text1 + text2) # Output: Hello World
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list1 + list2) # Output: [1, 2, 3, 4, 5, 6]
tuple1 = (10, 20)
tuple2 = (30, 40)
print(tuple1 + tuple2) # Output: (10, 20, 30, 40)
# Repetición con *
print("Ha" * 3) # Output: HaHaHa
print([0] * 5) # Output: [0, 0, 0, 0, 0]
print((1, 2) * 3) # Output: (1, 2, 1, 2, 1, 2)Importante: los rangos no admiten concatenación ni repetición:
r1 = range(5)
r2 = range(5, 10)
# combined = r1 + r2 # TypeError: unsupported operand type(s) for +: 'range' and 'range'
# Para combinar rangos, conviértelos primero a listas o tuplas
combined = list(r1) + list(r2)
print(combined) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]Contar apariciones
El método count() devuelve cuántas veces aparece un elemento:
# Strings: cuenta apariciones de substrings
text = "Mississippi"
print(text.count("s")) # Output: 4
print(text.count("ss")) # Output: 2
print(text.count("i")) # Output: 4
# Listas
numbers = [1, 2, 3, 2, 4, 2, 5]
print(numbers.count(2)) # Output: 3
print(numbers.count(6)) # Output: 0
# Tuplas
grades = (85, 90, 85, 92, 85, 88)
print(grades.count(85)) # Output: 3
print(grades.count(95)) # Output: 0
# Los rangos no tienen el método count(), pero puedes convertir primero
nums = range(0, 20, 2)
nums_list = list(nums)
print(nums_list.count(10)) # Output: 1Encontrar el índice de elementos
El método index() devuelve la posición de la primera aparición:
# Strings
text = "Python Programming"
print(text.index("P")) # Output: 0 (first P)
print(text.index("Pro")) # Output: 7 (substring position)
# print(text.index("Java")) # ValueError: substring not found
# Listas
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # Output: 1 (first occurrence)
print(fruits.index("cherry")) # Output: 2
# print(fruits.index("grape")) # ValueError: 'grape' is not in list
# Tuplas
coordinates = (10, 20, 30, 20, 40)
print(coordinates.index(20)) # Output: 1 (first occurrence)
print(coordinates.index(40)) # Output: 4
# Los rangos no tienen el método index(), pero puedes convertir primero
nums = range(10, 50, 5)
nums_list = list(nums)
print(nums_list.index(25)) # Output: 3Si el elemento no se encuentra, index() lanza un ValueError. Para evitarlo, comprueba primero con in:
fruits = ["apple", "banana", "cherry"]
search_fruit = "grape"
if search_fruit in fruits:
position = fruits.index(search_fruit)
print(f"{search_fruit} found at position {position}")
else:
print(f"{search_fruit} not found")
# Output: grape not foundIteración con bucles for
Todas las secuencias se pueden iterar con bucles for:
# Strings: iterar sobre caracteres
for char in "Python":
print(char, end=" ")
print() # Output: P y t h o n
# Listas
for fruit in ["apple", "banana", "cherry"]:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherry
# Tuplas
for score in (85, 90, 78):
print(f"Score: {score}")
# Output:
# Score: 85
# Score: 90
# Score: 78
# Rangos
for i in range(1, 6):
print(f"Count: {i}")
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5Operaciones de comparación
Las secuencias se pueden comparar usando ==, !=, <, >, <= y >=:
# Igualdad
print([1, 2, 3] == [1, 2, 3]) # Output: True
print((1, 2, 3) == (1, 2, 3)) # Output: True
print("abc" == "abc") # Output: True
# Desigualdad
print([1, 2, 3] != [1, 2, 4]) # Output: True
print((1, 2) != (1, 2)) # Output: False
# Comparación lexicográfica (elemento por elemento)
print([1, 2, 3] < [1, 2, 4]) # Output: True (3 < 4)
print([1, 2, 3] < [1, 3, 0]) # Output: True (2 < 3)
print("apple" < "banana") # Output: True (alphabetical)
print((1, 2) < (1, 2, 3)) # Output: True (shorter is less if equal so far)
# Comparar tipos diferentes
print([1, 2, 3] == (1, 2, 3)) # Output: False (different types)La comparación funciona elemento por elemento de izquierda a derecha. El primer elemento diferente determina el resultado.
Comprender estas operaciones comunes te permite escribir código que funcione con cualquier tipo de secuencia, haciendo tus programas más flexibles y reutilizables.
15.9) Slicing avanzado en todos los tipos de secuencia
El slicing es una de las características más potentes de Python para trabajar con secuencias. Aunque introdujimos el slicing básico en el Capítulo 14, hay técnicas avanzadas de slicing que funcionan en todos los tipos de secuencia.
Repaso de slicing básico
El slicing extrae una parte de una secuencia usando la sintaxis sequence[start:stop:step]:
# Slicing básico con strings
text = "Python Programming"
print(text[0:6]) # Output: Python
print(text[7:18]) # Output: Programming
print(text[7:]) # Output: Programming (from index 7 to end)
print(text[:6]) # Output: Python (from start to index 6)
# Slicing básico con listas
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:7]) # Output: [2, 3, 4, 5, 6]
print(numbers[:5]) # Output: [0, 1, 2, 3, 4]
print(numbers[5:]) # Output: [5, 6, 7, 8, 9]
# Slicing básico con tuplas
coordinates = (10, 20, 30, 40, 50, 60)
print(coordinates[1:4]) # Output: (20, 30, 40)
print(coordinates[:3]) # Output: (10, 20, 30)
print(coordinates[3:]) # Output: (40, 50, 60)
# Slicing básico con rangos
nums = range(0, 100, 10)
print(list(nums[2:5])) # Output: [20, 30, 40]Recuerda: start es inclusivo, stop es exclusivo, y el resultado siempre es del mismo tipo que la secuencia original.
Usar step en el slicing
El tercer parámetro opcional step controla cuántos elementos se saltan:
# Cada segundo elemento
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # Output: [0, 2, 4, 6, 8]
print(numbers[1::2]) # Output: [1, 3, 5, 7, 9]
# Cada tercer elemento
text = "abcdefghijklmnop"
print(text[::3]) # Output: adgjmp
# step con start y stop
print(numbers[2:8:2]) # Output: [2, 4, 6]
print(text[1:10:2]) # Output: bdfhjStep negativo: invertir secuencias
Un step negativo invierte la dirección del slicing:
# Invertir secuencias completas
text = "Python"
print(text[::-1]) # Output: nohtyP
numbers = [1, 2, 3, 4, 5]
print(numbers[::-1]) # Output: [5, 4, 3, 2, 1]
coordinates = (10, 20, 30, 40)
print(coordinates[::-1]) # Output: (40, 30, 20, 10)
# Invertir con step
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::-2]) # Output: [9, 7, 5, 3, 1] (every second, backwards)
# Invertir una parte
text = "Python Programming"
print(text[7:18][::-1]) # Output: gnimmargorP (reverse "Programming")Al usar step negativo, start y stop funcionan de forma diferente:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Con step negativo, start debe ser mayor que stop
print(numbers[7:2:-1]) # Output: [7, 6, 5, 4, 3] (from 7 down to 3)
print(numbers[8:3:-2]) # Output: [8, 6, 4] (from 8 down to 4, step -2)
# Omitir start/stop con step negativo
print(numbers[:5:-1]) # Output: [9, 8, 7, 6] (from end down to 6)
print(numbers[5::-1]) # Output: [5, 4, 3, 2, 1, 0] (from 5 down to start)Índices negativos en el slicing
Puedes usar índices negativos para las posiciones start y stop:
text = "Python Programming"
# Últimos 11 caracteres
print(text[-11:]) # Output: Programming
# Todo excepto los últimos 11 caracteres
print(text[:-11]) # Output: Python
# De -15 a -5
print(text[-15:-5]) # Output: hon Progra
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Últimos 5 elementos
print(numbers[-5:]) # Output: [5, 6, 7, 8, 9]
# Todo excepto los últimos 3 elementos
print(numbers[:-3]) # Output: [0, 1, 2, 3, 4, 5, 6]
# De -7 a -2
print(numbers[-7:-2]) # Output: [3, 4, 5, 6, 7]Slicing con rangos
Hacer slicing a un rango devuelve un nuevo objeto range:
# Hacer slicing a rangos
numbers = range(0, 100, 5) # 0, 5, 10, 15, ..., 95
print(numbers) # Output: range(0, 100, 5)
# El slicing devuelve un nuevo range
subset = numbers[5:10]
print(subset) # Output: range(25, 50, 5)
print(list(subset)) # Output: [25, 30, 35, 40, 45]
# Con step
every_other = numbers[::2]
print(list(every_other)) # Output: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
# Step negativo
reversed_range = numbers[::-1]
print(list(reversed_range)) # Output: [95, 90, 85, ..., 5, 0]Slices vacíos y casos límite
numbers = [1, 2, 3, 4, 5]
# Slices vacíos (start >= stop con step positivo)
print(numbers[3:3]) # Output: []
print(numbers[5:10]) # Output: [] (stop beyond length)
print(numbers[10:20]) # Output: [] (both beyond length)
# Los slices fuera de los límites de la secuencia son seguros
print(numbers[-100:100]) # Output: [1, 2, 3, 4, 5] (entire sequence)
print(numbers[2:100]) # Output: [3, 4, 5] (from 2 to end)
# Step negativo con start/stop incompatibles
print(numbers[2:7:-1]) # Output: [] (can't go forward with negative step)
# No se permite step de 0
# print(numbers[::0]) # ValueError: slice step cannot be zeroSlicing para copiar
El slicing crea una secuencia nueva, lo que proporciona una forma de copiar:
# Copiar con slicing
original = [1, 2, 3, 4, 5]
copy = original[:] # Slice desde el inicio hasta el final
print(copy) # Output: [1, 2, 3, 4, 5]
# Modificar la copia no afecta al original
copy[0] = 100
print(f"Original: {original}") # Output: Original: [1, 2, 3, 4, 5]
print(f"Copy: {copy}") # Output: Copy: [100, 2, 3, 4, 5]
# Esto también funciona para tuplas (crea una nueva tupla)
original_tuple = (1, 2, 3, 4, 5)
copy_tuple = original_tuple[:]
print(copy_tuple) # Output: (1, 2, 3, 4, 5)
# Para strings
text = "Python"
text_copy = text[:]
print(text_copy) # Output: PythonSin embargo, recuerda del Capítulo 14 que esto crea una copia superficial(shallow copy).
# Limitación de la copia superficial
original = [[1, 2], [3, 4]]
copy = original[:]
# Modificar una lista anidada afecta a ambas
copy[0][0] = 100
print(f"Original: {original}") # Output: Original: [[100, 2], [3, 4]]
print(f"Copy: {copy}") # Output: Copy: [[100, 2], [3, 4]]Las tuplas y los rangos son herramientas esenciales en el conjunto de secuencias de Python. Las tuplas proporcionan datos inmutables y estructurados que protegen la información de modificaciones accidentales y permiten su uso como claves de diccionario. Los rangos ofrecen representaciones eficientes en memoria de secuencias numéricas, perfectas para bucles y secuencias grandes. Comprender cuándo usar cada tipo—y cómo convertir entre ellos—hace que tu código sea más eficiente, más seguro y más claro en su intención.
Las operaciones comunes compartidas por todos los tipos de secuencia—indexación, slicing, iteración, pruebas de pertenencia—forman una interfaz consistente que hace que trabajar con cualquier secuencia sea intuitivo. Las técnicas avanzadas de slicing te dan formas potentes y expresivas de extraer y manipular datos de secuencia.
A medida que sigas programando en Python, te encontrarás eligiendo de forma natural el tipo de secuencia adecuado para cada situación: listas para colecciones que cambian, tuplas para registros fijos, rangos para secuencias numéricas y strings para texto. Este capítulo te ha dado el conocimiento para tomar esas decisiones con confianza y usar cada tipo de forma efectiva.