🐍 Curso Python · Nivel N1–N2 Principiante / Básico

Bloque 2: Estructuras de datos

Los datos reales vienen en grupo: listas de ventas, usuarios, contactos, resultados. Aquí dominarás las cuatro colecciones fundamentales de Python — listas, tuplas, diccionarios y conjuntos — con índices, slicing, sus métodos más útiles y las compresiones, una de las características que hacen el código Python tan elegante. Terminarás con un gestor de inventario como proyecto integrador.

⏱ ≈ 16 horas 📚 5 lecciones ✍️ 14 ejercicios guiados 📝 Examen del bloque + reto integrador
Lección 1 · Teoría

Listas: el trabajo diario

Una lista (list) es una colección ordenada y modificable de elementos. Se crea con corchetes [] y puede contener cualquier tipo (incluso otros tipos mezclados, aunque lo normal es que sea homogénea).

crear listas
frutas = ["manzana", "pera", "uva"]
numeros = [1, 2, 3, 4, 5]
mixta = ["Ana", 25, True]
vacia = []

print(len(frutas))     # 3 — número de elementos
print(frutas[0])       # manzana — el primero
print(frutas[-1])      # uva — el último

1.1 · Añadir y quitar

mutar una lista
frutas = []
frutas.append("kivi")        # añade al final
frutas.append("mango")
frutas.insert(0, "sandía")  # inserta en la posición 0
frutas.remove("kivi")      # borra la primera aparición
frutas.pop()               # saca y devuelve el último
frutas.pop(0)              # saca y devuelve el 0
print(frutas)
⚠️ Una lista es mutable: append(), remove(), pop() la modifican en el sitio (no devuelven una copia nueva). Si haces b = a, ambas variables apuntan a la misma lista: lo que cambies en una se ve en la otra. Para copiar de verdad usa b = a.copy() o b = list(a).

1.2 · Recorrer listas

for sobre una lista
precios = [9.99, 19.99, 5.50]
for p in precios:
    print(f"Precio: {p:.2f} €")

# ¿Necesitas también la posición? → enumerate
for indice, p in enumerate(precios):
    print(indice, p)
💡 enumerate() devuelve pares (índice, valor). Es la forma Python de hacer el clásico «for con contador» sin gestionar tú el contador.
Lección 2 · Teoría

Índices y slicing

Toda colección ordenada (strings, listas, tuplas) se indexa desde 0. Los índices negativos cuentan desde el final: -1 es el último, -2 el penúltimo…

indexar
palabra = "Python"
print(palabra[0])    # P
print(palabra[2])    # t
print(palabra[-1])   # n

numeros = [10, 20, 30, 40, 50]
print(numeros[1])     # 20
print(numeros[-2])    # 40

2.1 · Slicing: cortar rebanadas

La sintaxis secuencia[inicio:fin:paso] extrae una rebanada. El fin no se incluye:

rebanadas
n = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(n[2:5])     # [2, 3, 4]        → del índice 2 al 4
print(n[:4])      # [0, 1, 2, 3]     → desde el inicio
print(n[6:])       # [6, 7, 8, 9]     → hasta el final
print(n[::2])      # [0, 2, 4, 6, 8]  → de dos en dos
print(n[::-1])     # [9, 8, ..., 0]   → la lista invertida
💡 texto[::-1] invierte un string. Es el truco más corto para el clásico «¿es esta palabra un palíndromo?».
⚠️ Si el índice no existe (n[15]) Python lanza IndexError. El slicing, en cambio, nunca da error: devuelve lo que exista (incluso [] vacío). Comprueba siempre len() antes de acceder a un índice manual.
Lección 3 · Teoría

Métodos y ordenación

Las listas vienen con métodos muy útiles. Los que cambian la lista (como sort, reverse o append) devuelven None; los que analizan (como count, index) devuelven un valor.

3.1 · Búsquedas y comprobaciones

buscar en listas
cola = ["a", "b", "c", "a"]
print("a" in cola)     # True — pertenencia
print(cola.count("a"))  # 2 — cuántas veces aparece
print(cola.index("b"))  # 1 — primera posición

3.2 · Ordenar y su función clave

ordenar listas
nombres = ["ana", "carlos", "bea"]
nombres.sort()              # ordena la lista EN EL SITIO (None)
print(nombres)               # ['ana', 'bea', 'carlos']

nums = [3, 1, 2]
nums.sort(reverse=True)     # de mayor a menor

# sorted() devuelve una copia ordenada (no modifica la original)
original = [4, 2]
ordenada = sorted(original)  # [2, 4] ; original sigue [4, 2]
💡 sorted(lista, key=funcion) ordena usando una función que extrae la clave. Por ejemplo, ordenar por la segunda letra: sorted(palabras, key=lambda p: p[1]). En el bloque 3 verás lambda a fondo.

3.3 · min, max y sum

funciones útiles sobre listas numéricas
notas = [7, 5, 9, 6]
print(min(notas))   # 5
print(max(notas))   # 9
print(sum(notas))   # 27
print(sum(notas) / len(notas))  # 6.75 → la media

3.4 · Unir listas y strings

split y join
frase = "Python es genial"
palabras = frase.split()       # ['Python', 'es', 'genial']

lista = ["a", "b", "c"]
unido = "-".join(lista)       # 'a-b-c'

# unir + listas con extensión
a = [1, 2]
a.extend([3, 4])          # [1, 2, 3, 4] — añade varios
Lección 4 · Teoría

Tuplas, conjuntos y diccionarios

4.1 · Tuplas: listas inmutables

Una tupla (tuple) se crea con paréntesis y es inmutable: una vez creada no puedes añadir ni quitar elementos. Se usa para datos que no deben cambiar (coordenadas, configuraciones, claves).

tuplas
punto = (3, 4)
x, y = punto          # desempacar: x=3, y=4
hyp = (punto[0] ** 2 + punto[1] ** 2) ** 0.5
print(f"Distancia al origen: {hyp:.2f}")  # 5.00
💡 Desempaquetado: a, b, c = [1, 2, 3] asigna cada nombre a cada valor. Funciona con listas, tuplas y hasta con range. Es la forma elegante de «leer» una colección fija.

4.2 · Conjuntos: valores únicos

Un conjunto (set) almacena valores únicos y hace operaciones matemáticas de conjuntos (unión, intersección…). Es la solución perfecta para «quitar duplicados», y ninguna otra estructura es más rápida para comprobar pertenencia.

conjuntos
a = {1, 2, 3, 3, 3}     # {1, 2, 3} — los duplicados desaparecen
b = {3, 4, 5}
print(a | b)        # {1,2,3,4,5} — unión
print(a & b)         # {3} — intersección
print(a - b)         # {1,2} — diferencia

precio_unico = set([5, 5, 7, 9])
print(precio_unico)  # {5, 7, 9} — cómo quitar duplicados

4.3 · Diccionarios: clave → valor

El diccionario (dict) es la estructura más importante de Python: asocia claves únicas con valores. Es el equivalente a un objeto JSON o a un array asociativo.

diccionarios
usuario = {
    "nombre": "Ana",
    "edad": 25,
    "ciudad": "Madrid",
}

print(usuario["nombre"])    # Ana
usuario["edad"] = 26       # actualizar
usuario["email"] = "ana@x.es"  # añadir (si la clave no existe)

print("nombre" in usuario)  # True
print(usuario.get("telefono", "sin teléfono"))  # sin teléfono → no da error
⚠️ usuario["clave"] lanza KeyError si la clave no existe. .get("clave", default) devuelve el valor por defecto sin lanzar error. En código moderno, usa casi siempre .get().

4.4 · Recorrer diccionarios

iterar un dict
stock = {"camiseta": 12, "pantalón": 5, "gorra": 20}

for producto in stock:          # itera sobre las claves
    print(producto)

for producto, cantidad in stock.items():   # clave y valor a la vez
    print(f"{producto}: {cantidad} uds")
Resumen de colecciones
TipoSintaxis¿Ordenado?¿Mutable?¿Duplicados?
list[1, 2]SíSíSí
tuple(1, 2)SíNoSí
set{1, 2}NoSíNo
dict{"a": 1}Sí (inserción)SíNo (claves)
Lección 5 · Teoría

Compresiones (comprehensions)

Una comprensión construye una colección en una sola línea: [expresión for x in if condición]. Sustituye al patrón clásico de «crear lista vacía → for → append» y es un sello de identidad de Python.

compresión de listas
# Método clásico
cuadrados = []
for i in range(1, 6):
    cuadrados.append(i ** 2)

# Comprensión (equivalente, en una línea)
cuadrados = [i ** 2 for i in range(1, 6)]
print(cuadrados)  # [1, 4, 9, 16, 25]

5.1 · Con filtro

comprensión con if
nums = list(range(0, 20))
pares = [n for n in nums if n % 2 == 0]
print(pares)  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

cars = ["fi", "al", "alta"]
largas = [c.upper() for c in cars if len(c) >= 2]

5.2 · Compresión de diccionarios y de conjuntos

dict y set comprehension
# dict: {clave: valor for ...}
cuadrados_dict = {i: i**2 for i in range(1, 4)}
print(cuadrados_dict)  # {1: 1, 2: 4, 3: 9}

# set: {expresión for ...}
letras = {c for c in "abracadabra" if c not in "bd"}
print(letras)  # {'a', 'r', 'c'} — únicas y filtradas
💡 La compresión con enumerate es muy útil: {str(i): v for i, v in enumerate(lista)} convierte una lista en un dict indexado por su posición. La usarás en los ejercicios de esta semana.
⚠️ Legibilidad ante todo. Una compresión vale para un mapa/filtro sencillo. Si la lógica tiene varios if o bucles anidados, usa un bucle normal: el código claro gana a la línea corta.
🎉 Con las cuatro colecciones y las compresiones ya sabes manejar datos en grupo de forma profesional. Ahora a por los ejercicios: vas a construir un gestor de inventario.
Manos a la obra

✍️ Los 14 ejercicios del bloque

Crea la carpeta python-b2/ y un archivo .py por ejercicio. Algunos piden estructuras de datos fijas que definiremos aquí para que los ejercicios sean consistentes: ventas = [120, 85, 200, 45, 90] y alumnos = {"Ana": 8, "Luis": 6, "Rita": 9, "Pablo": 4}.

Sección A · Listas

1 Lista de compras dinámica

Crea una lista vacía. Pide 3 productos con input(), añádelos con append() y muestra el resultado con print().

Ver solución
compras = []
for i in range(3):
    compras.append(input(f"Producto {i+1}: "))
print(compras)

2 Los últimos 3 elementos

Dada ventas = [120, 85, 200, 45, 90], muestra los 3 últimos elementos con slicing y calcula su suma con sum().

Ver solución
ventas = [120, 85, 200, 45, 90]
ultimas = ventas[-3:]
print(ultimas)          # [200, 45, 90]
print(sum(ultimas))    # 335

3 Días con más de 100 €

Con la misma lista ventas, cuenta cuántos valores superan 100 usando una compresión de listas y len().

Ver solución
ventas = [120, 85, 200, 45, 90]
altos = [v for v in ventas if v > 100]
print(len(altos))   # 2

4 Al revés

Dada nombres = ["Ana", "Luis", "Rita"], muestra la lista invertida usando slicing con paso −1, y después ordénala alfabéticamente con sort().

Ver solución
nombres = ["Ana", "Luis", "Rita"]
print(nombres[::-1])   # ['Rita', 'Luis', 'Ana']
nombres.sort()
print(nombres)          # ['Ana', 'Luis', 'Rita']

Sección B · Strings y tuplas

5 Palíndromo

Pide una palabra, pásala a minúsculas y comprueba si es un palíndromo comparándola con su versión invertida ([::-1]). Muestra el resultado.

Ver solución
palabra = input("Palabra: ").lower()
if palabra == palabra[::-1]:
    print("Es un palíndromo")
else:
    print("No es un palíndromo")

6 Desempaquetado

Dada la tupla info = ("Ana", 25, "Madrid"), desempácala en tres variables y muéstralas con una f-string.

Ver solución
info = ("Ana", 25, "Madrid")
nombre, edad, ciudad = info
print(f"{nombre}, {edad} años, de {ciudad}")

7 Sin duplicados

Dada numeros = [1, 2, 2, 3, 3, 3, 4], elimina los duplicados convirtiéndola en un set y vuelve a lista. Muestra el resultado y cuántos elementos únicos hay.

Ver solución
numeros = [1, 2, 2, 3, 3, 3, 4]
unicos = list(set(numeros))
print(unicos)          # [1, 2, 3, 4]
print(len(unicos))     # 4

8 Vocales en una frase

Pide una frase y cuenta cuántas vocales tiene recorriéndola y comprobando cada letra contra el conjunto {"a","e","i","o","u"}. Útil para practicar in con sets.

Ver solución
vocales = {"a", "e", "i", "o", "u"}
frase = input("Frase: ").lower()
total = 0
for letra in frase:
    if letra in vocales:
        total += 1
print(f"Tiene {total} vocales")

Sección C · Diccionarios

9 Media de notas en un dict

Con alumnos = {"Ana": 8, "Luis": 6, "Rita": 9, "Pablo": 4}, calcula la media mediante sum(alumnos.values()) / len(alumnos). Muestra el resultado con 2 decimales.

Ver solución
alumnos = {"Ana": 8, "Luis": 6, "Rita": 9, "Pablo": 4}
media = sum(alumnos.values()) / len(alumnos)
print(f"Media de la clase: {media:.2f}")

10 Aprobados y suspensos

Con el mismo dict alumnos, separa en dos listas los nombres con nota ≥ 5 y < 5, usando compresiones de listas sobre alumnos.items().

Ver solución
alumnos = {"Ana": 8, "Luis": 6, "Rita": 9, "Pablo": 4}
aprobados = [n for n, nota in alumnos.items() if nota >= 5]
suspensos = [n for n, nota in alumnos.items() if nota < 5]
print(aprobados, suspensos)

11 Mejor y peor alumno

Con alumnos, encuentra el nombre con nota máxima y mínima usando max() y min() con key=alumnos.get.

Ver solución
alumnos = {"Ana": 8, "Luis": 6, "Rita": 9, "Pablo": 4}
mejor = max(alumnos, key=alumnos.get)
peor = min(alumnos, key=alumnos.get)
print(f"Mejor: {mejor} ({alumnos[mejor]}), Peor: {peor} ({alumnos[peor]})")

12 Contador de letras

Pide una palabra y construye con una compresión de diccionarios un dict con cada letra y el número de veces que aparece: {letra: palabra.count(letra) for letra in ...}.

Ver solución
palabra = input("Palabra: ")
frecuencias = {letra: palabra.count(letra) for letra in set(palabra)}
print(frecuencias)  # por ejemplo {'h': 2, 'o': 1, ...}

13 Diccionarios anidados

Crea un dict de productos donde cada valor sea otro dict con precio y stock. Muestra el precio del segundo producto y el total del inventario (suma de precio * stock).

Ver solución
tienda = {
    "ratón": {"precio": 12, "stock": 30},
    "teclado": {"precio": 25, "stock": 10},
    "pantalla": {"precio": 150, "stock": 5},
}
print(tienda["teclado"]["precio"])   # 25
total = sum(p["precio"] * p["stock"] for p in tienda.values())
print(f"Valor total del inventario: {total} €")

14 🏆 Proyecto integrador: «gestor de inventario»

Crea inventario.py: un mini gestor con un diccionario de productos ({"camiseta": 12, "pantalón": 5, "gorra": 20}) y un menú infinito con while True:

  • 1 · ver — muestra cada producto y su stock con items().
  • 2 · añadir — pide nombre y stock; usa .get() para sumar al existente.
  • 3 · quitar — pide nombre y resta stock sin bajar de 0.
  • 4 · total — muestra el stock total con sum(values()).
  • salir — termina con break.
  • Usa un if para las opciones, comentarios # y nombres en snake_case.
Ver una solución posible
# Gestor de inventario con diccionarios
stock = {"camiseta": 12, "pantalón": 5, "gorra": 20}

while True:
    print("\n--- INVENTARIO ---")
    opcion = input("1 ver | 2 añadir | 3 quitar | 4 total | salir: ").strip().lower()

    if opcion == "1":          # ver inventario
        for producto, cantidad in stock.items():
            print(f"- {producto}: {cantidad} uds")

    elif opcion == "2":        # añadir stock
        nombre = input("Producto: ")
        cantidad = int(input("Cantidad: "))
        stock[nombre] = stock.get(nombre, 0) + cantidad

    elif opcion == "3":        # quitar stock (sin bajar de 0)
        nombre = input("Producto: ")
        if nombre in stock:
            stock[nombre] = max(stock[nombre] - int(input("Cantidad: ")), 0)

    elif opcion == "4":        # total de unidades
        print(f"Stock total: {sum(stock.values())} uds")

    elif opcion == "salir":
        break

    else:
        print("Opción no válida")
Evaluación del bloque

📝 Examen del Bloque 2

Dos partes: un test teórico autoevaluable (mínimo 70 %) y un reto práctico de autocorrección con laboratorio.

Parte A · Test teórico (aprueba con 11/15)

1 · ¿Qué estructura es inmutable (no se puede modificar)?
2 · ¿Qué método añade un elemento al final de una lista?
3 · ¿Qué genera nums[2:5] sobre nums = [0,1,2,3,4,5]?
4 · ¿Cómo se obtiene el último elemento de una lista?
5 · ¿Qué estructura elimina automáticamente los duplicados?
6 · ¿Qué método del diccionario NO lanza error si la clave no existe?
7 · ¿Qué devuelve "a-b-c".split("-")?
8 · ¿Qué itera sobre clave y valor a la vez en un dict?
9 · ¿Qué hace [n * 2 for n in range(3)]?
10 · ¿Cómo inviertes un string en Python?
11 · ¿Qué hace pop() en una lista?
12 · ¿Qué devuelve len({"a": 1, "b": 2})?
13 · ¿Cuál es la diferencia entre sort() y sorted()?
14 · ¿Con qué obtienes al mismo tiempo índice y valor en un bucle for?
15 · ¿Qué expresión cuenta cuántas veces aparece "a" en una lista?

Parte B · Reto «análisis de calificaciones»

Crea notas.py: dado un dict de alumnos y notas, muestra la media, quién aprueba y quién suspende, y la mejor nota. Valida tú mismo/a estos criterios. Mínimo para aprobar: 6 de 8 casillas.

Cumplidos: 0 / 8

🎉 ¿Superaste las dos partes? Bloque 2 completado. Ya manejas listas, tuplas, conjuntos y diccionarios con índices, slicing, métodos y compresiones. En el bloque 3 subimos la potencia: funciones y módulos para estructurar programas como es debido. 🐍🔥