Tuplas
Tuplas en Python
Qué es una tupla
Una tupla es una colección ordenada e inmutable de elementos. Es muy similar a una lista con una diferencia fundamental: una vez creada no se puede modificar. No puedes añadir, eliminar ni cambiar ningún elemento.
# Lista — mutable, se puede cambiar
lista = [1, 2, 3]
lista[0] = 99 # permitido
lista.append(4) # permitido
# Tupla — inmutable, no se puede cambiar
tupla = (1, 2, 3)
tupla[0] = 99 # TypeError: 'tuple' object does not support item assignment
tupla.append(4) # AttributeError: 'tuple' object has no attribute 'append'
Cómo crear tuplas
# Con paréntesis
coordenadas = (40.4168, -3.7038)
colores = ("rojo", "verde", "azul")
mixta = ("Ana", 22, 8.5, True)
# Sin paréntesis — Python infiere que es una tupla por las comas
punto = 3, 4
print(punto) # (3, 4)
print(type(punto)) # <class 'tuple'>
# Tupla vacía
vacia = ()
vacia = tuple()
# Tupla de un solo elemento — necesita la coma final obligatoriamente
un_elemento = (42,)
print(type(un_elemento)) # <class 'tuple'>
# Sin la coma NO es una tupla
no_es_tupla = (42)
print(type(no_es_tupla)) # <class 'int'> ← son solo paréntesis matemáticos
# Desde otro iterable
desde_lista = tuple([1, 2, 3])
desde_string = tuple("Python")
desde_rango = tuple(range(5))
print(desde_lista) # (1, 2, 3)
print(desde_string) # ('P', 'y', 't', 'h', 'o', 'n')
print(desde_rango) # (0, 1, 2, 3, 4)
Acceso a elementos
Igual que las listas, con índices que empiezan en 0 y admiten negativos.
colores = ("rojo", "verde", "azul", "amarillo", "naranja")
print(colores[0]) # rojo ← primero
print(colores[2]) # azul
print(colores[-1]) # naranja ← último
print(colores[-2]) # amarillo ← penúltimo
# Slicing — igual que en listas
print(colores[1:4]) # ('verde', 'azul', 'amarillo')
print(colores[:3]) # ('rojo', 'verde', 'azul')
print(colores[2:]) # ('azul', 'amarillo', 'naranja')
print(colores[::-1]) # ('naranja', 'amarillo', 'azul', 'verde', 'rojo')
Métodos de las tuplas
Las tuplas tienen solo dos métodos porque no pueden modificarse.
.count() — cuántas veces aparece un elemento
notas = (8.5, 6.0, 9.2, 6.0, 7.5, 6.0, 8.5)
print(notas.count(6.0)) # 3 ← aparece tres veces
print(notas.count(8.5)) # 2
print(notas.count(10.0)) # 0 ← no aparece, devuelve 0
# Uso práctico
resultados = ("victoria", "derrota", "empate", "victoria", "victoria")
victorias = resultados.count("victoria")
print(f"Victorias: {victorias} de {len(resultados)}")
# Victorias: 3 de 5
.index() — posición de la primera aparición
colores = ("rojo", "verde", "azul", "verde", "amarillo")
print(colores.index("verde")) # 1 ← primera aparición
print(colores.index("amarillo")) # 4
# Buscar a partir de una posición
print(colores.index("verde", 2)) # 3 ← segunda aparición buscando desde pos 2
# Si no existe lanza ValueError
try:
print(colores.index("naranja"))
except ValueError:
print("Color no encontrado") # Color no encontrado
# Uso práctico
dias = ("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")
dia_actual = "jueves"
numero_dia = dias.index(dia_actual) + 1
print(f"{dia_actual} es el día {numero_dia} de la semana")
# jueves es el día 4 de la semana
Operaciones con tuplas
Aunque no se pueden modificar sí se pueden usar en operaciones que crean nuevas tuplas.
# Concatenación — crea una nueva tupla
a = (1, 2, 3)
b = (4, 5, 6)
c = a + b
print(c) # (1, 2, 3, 4, 5, 6)
# Repetición
t = (1, 2) * 3
print(t) # (1, 2, 1, 2, 1, 2)
# Pertenencia
colores = ("rojo", "verde", "azul")
print("rojo" in colores) # True
print("amarillo" in colores) # False
print("rojo" not in colores) # False
# Longitud, máximo, mínimo, suma
numeros = (3, 1, 4, 1, 5, 9, 2, 6)
print(len(numeros)) # 8
print(max(numeros)) # 9
print(min(numeros)) # 1
print(sum(numeros)) # 31
# Comparación
print((1, 2, 3) == (1, 2, 3)) # True
print((1, 2, 3) < (1, 2, 4)) # True ← compara elemento a elemento
print((1, 2) < (1, 2, 3)) # True ← el más corto es "menor"
El desempaquetado de tuplas
El desempaquetado (unpacking) es una de las características más potentes y elegantes de Python. Permite asignar cada elemento de una tupla a una variable distinta en una sola línea.
Desempaquetado básico
# Sin desempaquetado — forma larga
punto = (3, 4)
x = punto[0]
y = punto[1]
# Con desempaquetado — forma elegante
punto = (3, 4)
x, y = punto
print(x) # 3
print(y) # 4
El número de variables debe coincidir con el número de elementos:
coordenadas = (40.4168, -3.7038, 667)
latitud, longitud, altitud = coordenadas
print(f"Latitud: {latitud}") # 40.4168
print(f"Longitud: {longitud}") # -3.7038
print(f"Altitud: {altitud}") # 667
Desempaquetado con * — capturar el resto
El asterisco captura todos los elementos que no tienen variable asignada y los agrupa en una lista.
numeros = (1, 2, 3, 4, 5, 6, 7)
primero, *resto = numeros
print(primero) # 1
print(resto) # [2, 3, 4, 5, 6, 7]
*inicio, ultimo = numeros
print(inicio) # [1, 2, 3, 4, 5, 6]
print(ultimo) # 7
primero, *medio, ultimo = numeros
print(primero) # 1
print(medio) # [2, 3, 4, 5, 6]
print(ultimo) # 7
# Ignorar elementos con _
primero, _, tercero, *_ = numeros
print(primero) # 1
print(tercero) # 3
Intercambio de variables sin variable auxiliar
# En otros lenguajes necesitas una variable temporal
a = 10
b = 20
temp = a
a = b
b = temp
# En Python con desempaquetado
a = 10
b = 20
a, b = b, a # intercambio en una sola línea
print(a) # 20
print(b) # 10
Desempaquetado en bucles for
# Lista de tuplas
puntos = [(1, 2), (3, 4), (5, 6)]
# Sin desempaquetado
for punto in puntos:
print(punto[0], punto[1])
# Con desempaquetado — mucho más legible
for x, y in puntos:
print(f"x={x}, y={y}")
# Con enumerate
nombres = ["Ana", "Carlos", "María"]
for i, nombre in enumerate(nombres, 1):
print(f"{i}. {nombre}")
# Con zip
notas = [8.5, 6.0, 9.2]
alumnos = ["Ana", "Carlos", "María"]
for alumno, nota in zip(alumnos, notas):
print(f"{alumno}: {nota}")
Desempaquetado de tuplas anidadas
datos = (("Ana", "García"), 22, (40.4168, -3.7038))
(nombre, apellido), edad, (lat, lon) = datos
print(nombre) # Ana
print(apellido) # García
print(edad) # 22
print(lat) # 40.4168
Desempaquetado en funciones
# Una función que devuelve múltiples valores realmente devuelve una tupla
def analizar_lista(numeros):
return min(numeros), max(numeros), sum(numeros) / len(numeros)
resultado = analizar_lista([8, 4, 9, 6, 7])
print(resultado) # (4, 9, 6.8) ← es una tupla
print(type(resultado)) # <class 'tuple'>
# Desempaquetando directamente
minimo, maximo, media = analizar_lista([8, 4, 9, 6, 7])
print(f"Min: {minimo}, Max: {maximo}, Media: {media:.1f}")
# Min: 4, Max: 9, Media: 6.8
# Si no necesitas todos los valores usa _ para ignorar
_, maximo, _ = analizar_lista([8, 4, 9, 6, 7])
print(f"Solo el máximo: {maximo}") # Solo el máximo: 9
Para qué se usan las tuplas
1. Datos que no deben cambiar — coordenadas, colores, configuración
# Coordenadas geográficas
MADRID = (40.4168, -3.7038)
BARCELONA = (41.3851, 2.1734)
VALENCIA = (39.4699, -0.3763)
# Colores RGB que no cambian
ROJO = (255, 0, 0)
VERDE = (0, 255, 0)
AZUL = (0, 0, 255)
BLANCO = (255, 255, 255)
# Configuración fija
RESOLUCIONES_PERMITIDAS = (
(1280, 720),
(1920, 1080),
(2560, 1440),
(3840, 2160)
)
2. Claves de diccionario
Las listas no pueden ser claves de diccionario porque son mutables. Las tuplas sí.
# Tabla de distancias entre ciudades
distancias = {
("Madrid", "Barcelona"): 621,
("Madrid", "Valencia"): 357,
("Barcelona", "Valencia"): 349,
}
origen = "Madrid"
destino = "Barcelona"
print(f"Distancia {origen}-{destino}: {distancias[(origen, destino)]} km")
# Distancia Madrid-Barcelona: 621 km
# Tabla de notas por alumno y asignatura
notas = {
("Ana", "Matemáticas"): 9.0,
("Ana", "Historia"): 7.5,
("Carlos", "Matemáticas"): 6.0,
}
print(notas[("Ana", "Matemáticas")]) # 9.0
3. Retorno de múltiples valores desde funciones
from datetime import datetime
def obtener_fecha_completa():
ahora = datetime.now()
return ahora.day, ahora.month, ahora.year, ahora.hour, ahora.minute
dia, mes, año, hora, minuto = obtener_fecha_completa()
print(f"Fecha: {dia:02d}/{mes:02d}/{año} {hora:02d}:{minuto:02d}")
def dividir_con_resto(dividendo, divisor):
if divisor == 0:
return None, None
cociente = dividendo // divisor
resto = dividendo % divisor
return cociente, resto
cociente, resto = dividir_con_resto(17, 5)
print(f"17 ÷ 5 = {cociente} con resto {resto}") # 17 ÷ 5 = 3 con resto 2
4. Datos estructurados en registros
# Lista de alumnos como tuplas — más compacta que diccionarios para datos fijos
alumnos = [
("Ana García", 22, "Madrid", 8.5),
("Carlos López", 20, "Barcelona", 6.0),
("María Ruiz", 21, "Valencia", 9.2),
]
for nombre, edad, ciudad, nota in alumnos:
estado = "Aprobado" if nota >= 5 else "Suspenso"
print(f"{nombre:<15} {edad} años {ciudad:<12} {nota} {estado}")
5. Tuplas con nombre — namedtuple
Las namedtuple del módulo collections permiten crear tuplas cuyos elementos tienen nombre, combinando la inmutabilidad de la tupla con la legibilidad del diccionario.
from collections import namedtuple
# Definir el tipo
Alumno = namedtuple("Alumno", ["nombre", "edad", "nota"])
Punto = namedtuple("Punto", ["x", "y"])
Color = namedtuple("Color", ["rojo", "verde", "azul"])
# Crear instancias
ana = Alumno("Ana García", 22, 8.5)
origen = Punto(0, 0)
rojo = Color(255, 0, 0)
# Acceder por nombre — mucho más legible que por índice
print(ana.nombre) # Ana García
print(ana.nota) # 8.5
print(origen.x) # 0
print(rojo.rojo) # 255
# También funcionan como tuplas normales
nombre, edad, nota = ana
print(nombre) # Ana García
print(ana[0]) # Ana García ← acceso por índice también funciona
# Convertir a diccionario
print(ana._asdict())
# OrderedDict([('nombre', 'Ana García'), ('edad', 22), ('nota', 8.5)])
# Crear nueva instancia con un campo modificado
ana_actualizada = ana._replace(nota=9.0)
print(ana_actualizada) # Alumno(nombre='Ana García', edad=22, nota=9.0)
print(ana) # Alumno(nombre='Ana García', edad=22, nota=8.5) ← sin cambios
# Lista de namedtuples
alumnos = [
Alumno("Ana García", 22, 8.5),
Alumno("Carlos López", 20, 6.0),
Alumno("María Ruiz", 21, 9.2),
]
# Ordenar por nota
por_nota = sorted(alumnos, key=lambda a: a.nota, reverse=True)
for a in por_nota:
print(f"{a.nombre:<15} {a.nota}")
Tuplas vs Listas — cuándo usar cada una
# USA TUPLAS cuando:
# 1. Los datos no cambiarán
DIAS_SEMANA = ("lunes", "martes", "miércoles", "jueves",
"viernes", "sábado", "domingo")
# 2. Representas un registro con campos fijos
alumno = ("Ana García", 22, "Madrid", 8.5)
# 3. Necesitas usar la colección como clave de diccionario
cache = {(1, 2): "resultado_1_2", (3, 4): "resultado_3_4"}
# 4. Devuelves múltiples valores de una función
def min_max(lista):
return min(lista), max(lista)
# 5. Quieres comunicar que los datos son constantes
CONFIGURACION = (1920, 1080, 60) # resolución y fps
# USA LISTAS cuando:
# 1. Los datos cambiarán
notas_alumno = [8.5, 6.0]
notas_alumno.append(9.2)
# 2. Necesitas añadir o eliminar elementos
carrito = ["laptop", "teclado"]
carrito.append("ratón")
# 3. El orden puede cambiar
resultados = [3, 1, 4, 1, 5]
resultados.sort()
Conversión entre tuplas y listas
# Lista a tupla — para hacer inmutable
lista_notas = [8.5, 6.0, 9.2, 7.5]
tupla_notas = tuple(lista_notas)
print(tupla_notas) # (8.5, 6.0, 9.2, 7.5)
# Tupla a lista — para poder modificar
coordenadas = (40.4168, -3.7038)
lista_coord = list(coordenadas)
lista_coord[0] = 41.0 # ahora sí podemos modificar
nueva_tupla = tuple(lista_coord)
print(nueva_tupla) # (41.0, -3.7038)
Ejemplo práctico completo
from collections import namedtuple
# Definición de tipos
Alumno = namedtuple("Alumno", ["nombre", "ciudad", "notas"])
Informe = namedtuple("Informe", ["media", "maxima", "minima", "aprobado"])
def calcular_informe(notas):
media = sum(notas) / len(notas)
return Informe(
media = round(media, 2),
maxima = max(notas),
minima = min(notas),
aprobado = media >= 5
)
def mostrar_alumno(alumno):
informe = calcular_informe(alumno.notas)
estado = "✓ Aprobado" if informe.aprobado else "✗ Suspenso"
print(f"\n{'─'*40}")
print(f"Alumno: {alumno.nombre}")
print(f"Ciudad: {alumno.ciudad}")
print(f"Notas: {alumno.notas}")
print(f"Media: {informe.media} {estado}")
print(f"Máxima: {informe.maxima}")
print(f"Mínima: {informe.minima}")
# Datos
alumnos = [
Alumno("Ana García", "Madrid", (8.5, 9.0, 7.8, 9.2)),
Alumno("Carlos López", "Barcelona", (4.8, 5.5, 4.2, 6.0)),
Alumno("María Ruiz", "Valencia", (9.5, 8.7, 9.8, 9.1)),
]
print("=== INFORME DE ALUMNOS ===")
for alumno in alumnos:
mostrar_alumno(alumno)
# Estadísticas globales
todas_las_medias = [
calcular_informe(a.notas).media for a in alumnos
]
mejor_alumno = max(alumnos, key=lambda a: calcular_informe(a.notas).media)
print(f"\n{'='*40}")
print(f"Media global: {sum(todas_las_medias)/len(todas_las_medias):.2f}")
print(f"Mejor alumno: {mejor_alumno.nombre}")
aprobados = sum(1 for a in alumnos if calcular_informe(a.notas).aprobado)
print(f"Aprobados: {aprobados}/{len(alumnos)}")
```
Salida:
```
=== INFORME DE ALUMNOS ===
────────────────────────────────────────
Alumno: Ana García
Ciudad: Madrid
Notas: (8.5, 9.0, 7.8, 9.2)
Media: 8.63 ✓ Aprobado
Máxima: 9.2
Mínima: 7.8
────────────────────────────────────────
Alumno: Carlos López
Ciudad: Barcelona
Notas: (4.8, 5.5, 4.2, 6.0)
Media: 5.13 ✓ Aprobado
Máxima: 6.0
Mínima: 4.2
────────────────────────────────────────
Alumno: María Ruiz
Ciudad: Valencia
Notas: (9.5, 8.7, 9.8, 9.1)
Media: 9.28 ✓ Aprobado
Máxima: 9.8
Mínima: 8.7
========================================
Media global: 7.68
Mejor alumno: María Ruiz
Aprobados: 3/3
Resumen
Las tuplas son ideales cuando los datos no deben cambiar, cuando necesitas usar una colección como clave de diccionario, cuando una función devuelve múltiples valores, o cuando quieres comunicar claramente que ciertos datos son constantes. El desempaquetado es su característica más práctica y hace el código mucho más legible que acceder por índice. Las namedtuple añaden legibilidad al dar nombres a cada campo sin perder la inmutabilidad ni la eficiencia de las tuplas.