Estructuras de datos en Python: listas, tuplas, diccionarios y conjuntos

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 20 min de lectura

Python incluye cuatro estructuras de datos en su biblioteca estándar que cubren el noventa por ciento de los casos de uso: la lista, la tupla, el diccionario y el conjunto. No son intercambiables. Cada una resuelve un problema diferente y elegir la correcta hace el código más claro, más seguro y más eficiente. Esta lección cubre las cuatro en detalle.

📋 Listas: la estructura más versátil de Python

Una lista es una secuencia ordenada y mutable que puede contener elementos de cualquier tipo, incluso mezclados y repetidos. Es la estructura más usada en Python por su flexibilidad.

# Crear listas
vacia = []
numeros = [1, 2, 3, 4, 5]
mixta = [42, "hola", True, 3.14, None]
anidada = [[1, 2], [3, 4], [5, 6]]    # lista de listas

# Acceder por índice (empieza en 0, negativo desde el final)
frutas = ["manzana", "pera", "naranja", "uva"]
frutas[0]     # "manzana"
frutas[-1]    # "uva"
frutas[-2]    # "naranja"

# Modificar
frutas[1] = "kiwi"         # ahora ["manzana","kiwi","naranja","uva"]

Métodos esenciales de lista

L = [3, 1, 4, 1, 5, 9]

L.append(2)          # añade al final:       [..., 2]
L.insert(0, 0)       # inserta en posición:  [0, 3, 1, ...]
L.extend([6, 5])     # concatena otra lista
L.remove(1)          # elimina primera aparición de 1
ultimo = L.pop()     # saca y devuelve el último elemento
tercero = L.pop(2)   # saca el elemento en índice 2
L.sort()             # ordena in-place (modifica L)
L.sort(reverse=True) # ordena descendente
L.reverse()          # invierte in-place
L.index(4)           # devuelve índice de la primera aparición de 4
L.count(1)           # cuántas veces aparece 1
L.clear()            # vacía la lista

# Funciones built-in sobre listas:
len(L)               # longitud
sum(L)               # suma (si son números)
min(L), max(L)       # mínimo y máximo
sorted(L)            # nueva lista ordenada (no modifica L)
reversed(L)          # iterador inverso
Estantería de madera oscura llena de libros de colores ordenados en filas
Una estantería: elementos ordenados, accesibles por posición, que puedes añadir o retirar. La metáfora perfecta de una lista de Python. Fuente: Pexels (licencia libre).

✂️ Slicing: extraer sublistas con elegancia

El slicing (rebanado) permite extraer partes de una lista, string o tupla con la sintaxis [inicio:fin:paso]. El índice fin es siempre excluido.

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

L[2:5]      # [2, 3, 4]           — desde índice 2 hasta el 4
L[:3]       # [0, 1, 2]           — los primeros 3
L[7:]       # [7, 8, 9]           — desde el índice 7 hasta el final
L[-3:]      # [7, 8, 9]           — los últimos 3
L[::2]      # [0, 2, 4, 6, 8]    — cada 2 (pares)
L[::-1]     # [9, 8, 7, ..., 0]  — invertir
L[1:8:3]    # [1, 4, 7]           — inicio=1, fin=8, paso=3

# En strings:
"Python"[::-1]       # "nohtyP"
"abcdefg"[2:5]       # "cde"

# Crear una copia superficial:
copia = L[:]
copia = list(L)     # equivalente

# Modificar con slicing:
L[1:4] = [10, 20]   # reemplaza elementos 1, 2, 3 por 10 y 20
💡 Índices negativos: Python indexa desde el final con negativos. -1 es el último, -2 el penúltimo. L[-3:] es "los últimos tres", independientemente del tamaño de la lista.

📌 Tuplas: inmutabilidad como contrato

Una tupla es como una lista, pero inmutable: una vez creada no puedes cambiar, añadir ni eliminar sus elementos. Esa restricción comunica intención: "estos datos son fijos".

# Crear tuplas
punto = (10, 20)
rgb   = (255, 128, 0)
tupla_un_elemento = (42,)    # la coma es obligatoria
sin_parentesis = 1, 2, 3     # los paréntesis son opcionales

# Acceder (igual que lista, sin modificar)
punto[0]     # 10
punto[-1]    # 20

# Intentar modificar → TypeError
punto[0] = 5    # TypeError: 'tuple' object does not support item assignment

# Desempaquetado (muy habitual en Python):
x, y = punto
print(x, y)    # 10 20

# Con * para el resto:
primero, *resto = (1, 2, 3, 4, 5)
print(primero)  # 1
print(resto)    # [2, 3, 4, 5]

# Intercambio sin variable temporal (solo Python):
a, b = 1, 2
a, b = b, a
print(a, b)    # 2 1

# Las tuplas son hashables → usables como claves de dict
tablero = {}
tablero[(0, 0)] = "X"
tablero[(0, 1)] = "O"
💡 Tupla vs lista: La tupla es más rápida en acceso y ocupa menos memoria que la lista equivalente. Úsala para datos que conceptualmente no cambian: coordenadas, configuraciones, valores de retorno múltiple.
Infografía comparativa de las cuatro estructuras de datos de Python: lista, tupla, diccionario y conjunto
Las cuatro estructuras de datos de Python: sintaxis, propiedades (orden, mutabilidad, duplicados), métodos clave, cuándo usar cada una y coste computacional de las operaciones principales. Infografía: Ciberaula.

🗂️ Diccionarios: datos con nombre y acceso O(1)

Un diccionario almacena pares clave: valor. Las claves deben ser únicas y hashables (strings, números, tuplas). El acceso por clave es O(1) — prácticamente instantáneo sin importar el tamaño del diccionario.

# Crear diccionarios
vacio = {}
persona = {"nombre": "Ana", "edad": 28, "ciudad": "Madrid"}

# dict() con keywords:
config = dict(host="localhost", puerto=5432, debug=True)

# Acceder
persona["nombre"]             # "Ana"
persona.get("email")          # None (sin KeyError)
persona.get("email", "N/A")   # "N/A" como valor por defecto

# Modificar / añadir
persona["edad"] = 29          # modifica
persona["email"] = "ana@..."  # añade nueva clave

# Eliminar
del persona["ciudad"]
persona.pop("email")          # elimina y devuelve el valor
persona.pop("tel", None)      # sin error si no existe

# Iterar
for clave in persona:                    # solo claves
    print(clave)
for valor in persona.values():           # solo valores
    print(valor)
for clave, valor in persona.items():     # pares
    print(f"{clave}: {valor}")

# Comprobar existencia
"nombre" in persona     # True
"email" in persona      # False

# Métodos útiles
persona.keys()           # dict_keys([...])
persona.values()         # dict_values([...])
persona.items()          # dict_items([(k,v), ...])
persona.update({"edad": 30, "pais": "España"})  # merge in-place

# Python 3.9+: merge con |
ampliado = persona | {"rol": "admin"}    # nuevo dict

# setdefault: añade solo si la clave no existe
persona.setdefault("puntos", 0)
Cuatro cucharas de madera con especias de colores distintos sobre superficie de madera
Cada cuchara tiene su especia: una clave y su valor. Exactamente como un diccionario de Python, donde cada clave da acceso inmediato al valor que contiene. Fuente: Pexels (licencia libre).

Patrones avanzados con diccionarios

# defaultdict: valor por defecto automático
from collections import defaultdict

contador = defaultdict(int)    # int() = 0 por defecto
palabras = ["hola", "mundo", "hola", "python", "hola"]
for p in palabras:
    contador[p] += 1
# defaultdict(int, {'hola': 3, 'mundo': 1, 'python': 1})

# Counter: contar ocurrencias directamente
from collections import Counter
c = Counter(palabras)
c.most_common(2)    # [('hola', 3), ('mundo', 1)]

# Eliminar duplicados preservando orden (Python 3.7+):
lista_con_dups = [3, 1, 4, 1, 5, 9, 2, 6, 5]
sin_dups = list(dict.fromkeys(lista_con_dups))
# [3, 1, 4, 5, 9, 2, 6]

🔵 Conjuntos: unicidad y operaciones matemáticas

Un conjunto (set) es una colección sin orden y sin duplicados. La búsqueda en un set es O(1), igual que en un dict. Sus operaciones matemáticas (unión, intersección, diferencia) son especialmente útiles.

# Crear conjuntos
vacio = set()               # no confundir con {} que es dict vacío
colores = {"rojo", "verde", "azul"}
desde_lista = set([1, 2, 2, 3, 3, 3])   # {1, 2, 3}

# Añadir / eliminar
colores.add("amarillo")
colores.discard("verde")    # sin error si no existe
colores.remove("azul")      # KeyError si no existe

# Pertenencia — muy rápida
"rojo" in colores    # True

# Operaciones de conjunto
A = {1, 2, 3, 4, 5}
B = {3, 4, 5, 6, 7}

A | B        # unión:              {1,2,3,4,5,6,7}
A & B        # intersección:       {3,4,5}
A - B        # diferencia A-B:     {1,2}
B - A        # diferencia B-A:     {6,7}
A ^ B        # diferencia simétrica: {1,2,6,7}
A.issubset(B)        # ¿A ⊆ B?
A.issuperset(B)      # ¿A ⊇ B?
A.isdisjoint(B)      # ¿A ∩ B = ∅?

# Caso práctico: encontrar elementos comunes entre dos listas
lista1 = ["Ana", "Luis", "Marta", "Pedro"]
lista2 = ["Luis", "Carlos", "Ana", "Sara"]

comunes     = set(lista1) & set(lista2)   # {"Ana", "Luis"}
solo_en_1   = set(lista1) - set(lista2)   # {"Marta", "Pedro"}
todos       = set(lista1) | set(lista2)

# frozenset: versión inmutable (usable como clave de dict)
fs = frozenset({1, 2, 3})

⚡ Comprensiones: listas, dicts y sets en una línea

Las comprensiones son la forma idiomática de Python para crear colecciones a partir de otras, con lógica de transformación y filtrado integrada.

# List comprehension: [expresión for x in iterable if condición]
cuadrados   = [x**2 for x in range(1, 11)]
pares       = [x for x in range(20) if x % 2 == 0]
mayusculas  = [s.upper() for s in ["hola", "mundo"]]

# Comprensión anidada — aplanar lista de listas:
matriz   = [[1,2,3],[4,5,6],[7,8,9]]
plana    = [x for fila in matriz for x in fila]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Dict comprehension: {clave: valor for x in iterable}
cuadrados_d  = {x: x**2 for x in range(1, 6)}
# {1:1, 2:4, 3:9, 4:16, 5:25}

invertido    = {v: k for k, v in {"a":1,"b":2,"c":3}.items()}
# {1:"a", 2:"b", 3:"c"}

# Set comprehension: {expresión for x in iterable}
iniciales    = {nombre[0] for nombre in ["Ana","Arturo","Beatriz"]}
# {"A", "B"}

# Solo pares únicos de una lista con duplicados:
lista        = [1, 2, 2, 3, 3, 3, 4]
unicos_pares = {x for x in lista if x % 2 == 0}
# {2, 4}
Infografía: slicing, list comprehension, dict comprehension y operaciones avanzadas de listas y dicts en Python
Slicing con todos sus patrones, comprensiones de lista y diccionario, y operaciones avanzadas frecuentes (ordenar, deduplicar, aplanar, fusionar dicts, Counter). Infografía: Ciberaula.

🧭 Cómo elegir la estructura correcta

Una regla rápida para tomar la decisión correcta:

# ¿Colección ordenada que cambia?          → lista
tareas = ["comprar", "estudiar", "cocinar"]

# ¿Datos fijos que no deben cambiar?       → tupla
coordenadas = (40.4168, -3.7038)   # Madrid
pixel_color = (255, 128, 0)

# ¿Necesitas acceder por nombre/clave?     → dict
usuario = {"id": 1, "nombre": "Ana", "rol": "admin"}

# ¿Necesitas unicidad o comparar grupos?   → set
etiquetas_vistas = {"python", "django", "flask"}
permisos_usuario & permisos_requeridos    # intersección

# Tabla de decisión rápida:
# ┌─────────────────────────────┬──────────┐
# │ ¿Necesitas...?              │ Usar     │
# ├─────────────────────────────┼──────────┤
# │ Orden + mutabilidad         │ lista    │
# │ Orden + inmutabilidad       │ tupla    │
# │ Clave → valor               │ dict     │
# │ Sin duplicados / conjuntos  │ set      │
# └─────────────────────────────┴──────────┘

🛠️ Programa completo: gestor de contactos

from collections import defaultdict

# Cada contacto es un dict; todos los contactos en una lista
contactos = []

def buscar_por_nombre(nombre):
    """Busca contactos cuyo nombre contenga el texto."""
    nombre = nombre.lower()
    return [c for c in contactos if nombre in c["nombre"].lower()]

def buscar_por_etiqueta(etiqueta):
    """Devuelve contactos que tienen una etiqueta concreta."""
    return [c for c in contactos if etiqueta in c.get("etiquetas", set())]

def agregar_contacto(nombre, telefono, etiquetas=None):
    """Añade un nuevo contacto si el teléfono no está duplicado."""
    telefonos = {c["telefono"] for c in contactos}    # set para O(1)
    if telefono in telefonos:
        print(f"  Ya existe un contacto con el teléfono {telefono}.")
        return False
    contactos.append({
        "nombre": nombre,
        "telefono": telefono,
        "etiquetas": set(etiquetas) if etiquetas else set(),
    })
    return True

def resumen_etiquetas():
    """Cuenta cuántos contactos hay por etiqueta."""
    conteo = defaultdict(int)
    for c in contactos:
        for tag in c.get("etiquetas", set()):
            conteo[tag] += 1
    return dict(sorted(conteo.items(), key=lambda x: -x[1]))

# Cargar datos de ejemplo
datos = [
    ("Ana García",   "600111222", ["amigo", "trabajo"]),
    ("Luis López",   "600333444", ["familia"]),
    ("Marta Ruiz",   "600555666", ["amigo"]),
    ("Pedro Sanz",   "600777888", ["trabajo"]),
    ("Sara Díez",    "600999000", ["amigo", "familia"]),
]
for nombre, tel, tags in datos:
    agregar_contacto(nombre, tel, tags)

# Menú interactivo
while True:
    print("\n=== GESTOR DE CONTACTOS ===")
    print("1. Ver todos  2. Buscar nombre  3. Buscar etiqueta")
    print("4. Añadir     5. Resumen tags   6. Salir")
    opcion = input("Opción: ").strip()

    if opcion == "1":
        for c in sorted(contactos, key=lambda x: x["nombre"]):
            tags = ", ".join(sorted(c["etiquetas"])) or "sin etiquetas"
            print(f"  {c['nombre']:<20} {c['telefono']}  [{tags}]")

    elif opcion == "2":
        texto = input("  Nombre a buscar: ").strip()
        resultado = buscar_por_nombre(texto)
        if resultado:
            for c in resultado:
                print(f"  {c['nombre']} — {c['telefono']}")
        else:
            print("  Sin resultados.")

    elif opcion == "3":
        tag = input("  Etiqueta: ").strip()
        resultado = buscar_por_etiqueta(tag)
        print(f"  {len(resultado)} contacto(s) con etiqueta '{tag}':")
        for c in resultado:
            print(f"    {c['nombre']}")

    elif opcion == "4":
        nombre = input("  Nombre: ").strip()
        tel    = input("  Teléfono: ").strip()
        tags   = input("  Etiquetas (separadas por coma): ").strip()
        lista_tags = [t.strip() for t in tags.split(",")] if tags else []
        if agregar_contacto(nombre, tel, lista_tags):
            print("  Contacto añadido.")

    elif opcion == "5":
        resumen = resumen_etiquetas()
        for tag, n in resumen.items():
            print(f"  {tag:<15} {n} contacto(s)")

    elif opcion == "6":
        print("Hasta luego.")
        break

🐛 Errores clásicos con estructuras de datos

1. Modificar una lista mientras la iteras

# ❌ Comportamiento indefinido
for x in lista:
    if x < 0:
        lista.remove(x)    # modifica la lista durante la iteración

# ✅ Iterar sobre una copia o usar comprensión
lista = [x for x in lista if x >= 0]

2. Crear un set vacío con {}

vacio = {}         # ← dict vacío, NO set
vacio = set()      # ← set vacío ✅
type({})           # 
type(set())        # 

3. Usar dict["clave"] cuando puede no existir

datos = {"nombre": "Ana"}

datos["email"]              # KeyError: 'email'
datos.get("email")          # None  ✅
datos.get("email", "N/A")   # "N/A" ✅

4. Guardar el resultado de list.sort()

ordenada = lista.sort()    # sort() devuelve None
print(ordenada)            # None  ← trampa

ordenada = sorted(lista)   # sorted() devuelve lista nueva ✅

5. Confundir list() con []

a = [1, 2, 3]
b = a            # b apunta al MISMO objeto
b.append(4)
print(a)         # [1, 2, 3, 4]  ← a también cambió

b = a[:]         # copia superficial ✅
b = list(a)      # copia superficial ✅
import copy
b = copy.deepcopy(a)   # copia profunda (para listas anidadas)

✅ Resumen y próximos pasos

Listas para colecciones ordenadas que cambian. Tuplas para datos fijos que comunican inmutabilidad. Diccionarios para acceso por clave con rendimiento O(1). Conjuntos para unicidad y operaciones matemáticas. Slicing para extraer sublistas elegantemente. Comprensiones para transformar y filtrar en una línea.

La siguiente lección: manejo de errores con try/except — cómo escribir código que falla de forma controlada y recuperable.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Estructuras de datos en Python: listas, tuplas, diccionarios y conjuntos

Las dudas más comunes respondidas de forma clara y directa.

Usa lista cuando la colección va a cambiar: añadir, eliminar o modificar elementos. Usa tupla cuando los datos son fijos y no deben cambiar: coordenadas, colores RGB, registros de base de datos, valores de retorno de funciones. La tupla comunica intención al lector del código: "esto no cambia". Además, las tuplas son ligeramente más rápidas y pueden usarse como claves de diccionario porque son hashables.
dict["clave"] lanza KeyError si la clave no existe, lo que puede romper el programa. dict.get("clave") devuelve None por defecto si la clave no existe, y puedes especificar otro valor por defecto: dict.get("clave", "valor_por_defecto"). Usa corchetes cuando estés seguro de que la clave existe y quieras que falle ruidosamente si no. Usa get() cuando la ausencia de la clave sea un caso válido que quieras manejar.
list.sort() ordena la lista en su lugar (modifica la original) y devuelve None. sorted() devuelve una lista nueva ordenada sin modificar la original. Úsalos así: lista.sort() cuando no necesitas la versión original. nueva = sorted(lista) cuando quieres conservar ambas. sorted() también funciona sobre cualquier iterable, no solo listas.
Tienes tres opciones: del d["clave"] elimina la clave y lanza KeyError si no existe. d.pop("clave") elimina y devuelve el valor, y acepta un segundo argumento por defecto para evitar el error. d.pop("clave", None) es la forma más segura. También puedes usar d.popitem() para eliminar y devolver el último par insertado (útil para procesar dicts como colas).
No. Los conjuntos (set) no garantizan ningún orden. Si necesitas unicidad y orden, la solución idiomática es dict.fromkeys(lista) que elimina duplicados preservando el orden de primera aparición, porque los diccionarios en Python 3.7+ mantienen el orden de inserción. Otra opción es usar sorted(set(lista)) si el orden importa pero no necesariamente el de inserción.
Una comprensión anidada tiene dos o más cláusulas for. El caso más habitual es aplanar una lista de listas: [x for fila in matriz for x in fila]. Más de dos niveles de anidamiento hace el código difícil de leer y en esos casos es mejor usar bucles for explícitos o la función itertools.chain. La regla es: si no puedes leer la comprensión de un vistazo, usa un bucle.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Estructuras de datos en Python: listas, tuplas, diccionarios y conjuntos? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!