Python & AI Tutorials Logo
Programación Python

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:

python
# 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: 0

Las 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.

python
# 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:

python
# 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:

python
# 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:

python
# 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.

Varios elementos

Un solo elemento

Vacía

Creación de tuplas

Elección de sintaxis

(item1, item2, ...)

(item,) - se requiere coma

()

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:

python
# 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:

python
# 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.0

La 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:

python
# 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:

python
# 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:

python
# 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, 5

Observa 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:

python
# 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 expressions

Ignorar 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:

python
# 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.com

El 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

python
# 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: 78

El 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

python
# 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 deletion

Cuando 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

python
# 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:

python
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:

python
# 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:

python
# 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, longitude

Cuando 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:

python
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 seconds

15.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

python
# 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

python
# 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 tupla

Pié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:

python
# 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.

python
# 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:

python
# 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 assignment

Cuando 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:

python
# 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 crezca

Este 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:

python
# 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:

python
# 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 usuarios

Las 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:

python
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.6

Devolver 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:

python
# 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): Obstacle

Las 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:

python
# 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

python
# 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:

python
# 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:

python
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:

python
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:

python
# 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 1

Indexación y slicing de objetos range

Los objetos range admiten indexación y slicing igual que otras secuencias:

python
# 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: 8

Comprobar pertenencia

Puedes comprobar si un número está en un rango usando el operador in:

python
# 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

python
# 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

python
# 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:

python
# 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:

python
# 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.

list

tuple

tuple

list

Rango

Lista

Tupla

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():

python
# 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: 45

Para 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:

python
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:

python
# 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:

python
# 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 *:

python
# 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:

python
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:

python
# 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: 1

Encontrar el índice de elementos

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

python
# 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: 3

Si el elemento no se encuentra, index() lanza un ValueError. Para evitarlo, comprueba primero con in:

python
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 found

Iteración con bucles for

Todas las secuencias se pueden iterar con bucles for:

python
# 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: 5

Operaciones de comparación

Las secuencias se pueden comparar usando ==, !=, <, >, <= y >=:

python
# 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]:

python
# 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:

python
# 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: bdfhj

Step negativo: invertir secuencias

Un step negativo invierte la dirección del slicing:

python
# 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:

python
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:

python
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:

python
# 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

python
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 zero

Slicing para copiar

El slicing crea una secuencia nueva, lo que proporciona una forma de copiar:

python
# 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: Python

Sin embargo, recuerda del Capítulo 14 que esto crea una copia superficial(shallow copy).

python
# 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.

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