Cerrábamos el módulo 4 con una promesa: las dos limitaciones de TareaFácil —que solo guarda una tarea y que los datos de esa tarea viven en seis variables sueltas— se resuelven con estructuras de datos. Esta lección ataca la primera y más urgente: cómo guardar muchos valores bajo un solo nombre.

La herramienta se llama lista, y es la estructura de datos que más vas a usar en tu vida como programador: guarda una secuencia ordenada de valores, crece y mengua sobre la marcha, se recorre con un for y se puede ordenar y filtrar. En cuanto la tengas, Marta podrá llevar a la vez el cartel de la feria del libro, el menú de la Panadería Solé y el logotipo del cliente Vidal. Y de paso cerraremos los cabos de 04-05: sorted(key=...), map y filter necesitaban una colección sobre la que trabajar, y ya la tenemos.

Contenido

  1. De las variables sueltas a la lista
  2. Arreglo clásico y lista de Python
  3. Crear listas, medirlas e indexarlas
  4. Rebanadas: [inicio:fin:paso]
  5. Mutabilidad, aliasing y copias
  6. Métodos y funciones esenciales
  7. Recorrer: for, enumerate, zip e in
  8. Comprensiones de listas
  9. sorted con key, map y filter
  10. TareaFácil v0.8: listas paralelas
  11. Errores comunes y consejos
  12. Ejercicios
  13. Conclusión

  1. De las variables sueltas a la lista

Para guardar los días estimados de tres tareas, con lo que sabes hasta ahora solo hay una salida: dias_1 = 3, dias_2 = 5, dias_3 = 2. Funciona y a la vez es un callejón sin salida: no puedes recorrer esas variables con un bucle (cada nombre es distinto y no forman ninguna secuencia), no puedes añadir una cuarta tarea sin editar el programa, no puedes preguntar cuántas hay ni ordenarlas. Una lista convierte esas tres variables en un solo objeto con estructura:

dias = [3, 5, 2]
print(len(dias))        # 3
print(sum(dias))        # 10
dias.append(8)          # una cuarta tarea, en tiempo de ejecucion
print(dias)             # [3, 5, 2, 8]

La regla que se deriva de aquí vale para siempre: cuando varias variables se llaman igual salvo por un número, lo que quieres es una lista.

  1. Arreglo clásico y lista de Python

En la literatura de programación aparece la palabra arreglo (array), que no es exactamente lo mismo que una lista de Python. Un arreglo clásico, tal como existe en C, Java o Pascal, es un bloque contiguo de memoria con dos restricciones fuertes —tamaño fijo y un único tipo de dato— a cambio de velocidad y consumo mínimo de memoria. La lista de Python es dinámica y heterogénea.

Característica Arreglo clásico Lista de Python
Tamaño Fijo al crearse Crece y mengua solo
Tipos de los elementos Todos iguales Cualquier mezcla
Acceso por índice Sí, desde 0 Sí, desde 0
Insertar en medio Manual y costoso insert()
Consumo de memoria Mínimo Mayor: guarda referencias
En Python Módulo array, NumPy Tipo nativo list

Que ["Cartel feria", 3, True, 4.5] sea legal no significa que convenga. Una lista homogénea —todos los elementos del mismo tipo y con el mismo significado— es mucho más fácil de recorrer, porque dentro del for sabes qué tienes entre manos. Si cada posición significa una cosa distinta, lo que buscas es una tupla (05-04) o un diccionario (05-03). Y cuando necesites millones de números y la memoria importe, existen el módulo array de la biblioteca estándar y la biblioteca externa NumPy, muy usada en cálculo numérico: no los desarrollamos aquí, pero conviene saber que están.

  1. Crear listas, medirlas e indexarlas

Hay tres formas habituales de crear una lista, y len() para medirla:

equipo = ["Marta", "Luis", "Nuria"]     # literal
letras = list("Alba")                   # con list(): ['A', 'l', 'b', 'a']
pendientes = []                         # vacia, para llenarla despues
print(len(equipo), len(pendientes))     # 3 0

La lista vacía es el punto de partida del patrón acumulador de 03-02, solo que ahora acumulas elementos en vez de sumar números. len() es la misma función que usabas con cadenas: ambas son secuencias. Cada elemento tiene una posición, y esa posición empieza en cero:

titulos = ["Cartel feria del libro", "Menu Panaderia Sole", "Logotipo cliente Vidal"]
print(titulos[0])       # Cartel feria del libro
print(titulos[2])       # Logotipo cliente Vidal
print(titulos[3])       # IndexError: list index out of range

Una lista de tres elementos tiene índices 0, 1 y 2: el último índice válido es siempre len(lista) - 1. Los índices negativos cuentan desde el final y ahorran calcular longitudes: en esta lista, -1 es el mismo elemento que 2, -2 el mismo que 1 y -3 el mismo que 0. Por eso titulos[-1] es la forma idiomática de decir «el último», mucho mejor que titulos[len(titulos) - 1]. No existe -0, porque -0 es 0.

  1. Rebanadas: [inicio:fin:paso]

Una rebanada (slice) extrae un trozo y devuelve una lista nueva. Los tres números son opcionales. La regla clave, y la fuente eterna de confusión: inicio se incluye, fin NO. La ventaja del convenio es que fin - inicio da directamente cuántos elementos salen. Los ejemplos parten de dias = [3, 5, 2, 8, 1, 6].

Expresión Resultado Explicación
dias[1:4] [5, 2, 8] Del 1 al 3; el 4 queda fuera
dias[:3] [3, 5, 2] Desde el principio
dias[3:] [8, 1, 6] Hasta el final
dias[-2:] [1, 6] Los dos últimos
dias[::2] [3, 2, 1] De dos en dos, desde el principio
dias[::-1] [6, 1, 8, 2, 5, 3] Del revés
dias[:] [3, 5, 2, 8, 1, 6] Copia completa
dias[4:2] [] Rango imposible: vacía, sin error

Dos detalles importantes. Una rebanada nunca da IndexError aunque te pases de largo: dias[2:99] devuelve lo que haya. Y dias[:] parece redundante, pero es la forma clásica de obtener una copia superficial.

  1. Mutabilidad, aliasing y copias

Las listas son mutables: puedes cambiar su contenido sin crear una lista nueva, cosa que con las cadenas no podías (02-01). prioridades[1] = "alta" sustituye el segundo elemento y ya está. Hasta aquí, cómodo. Ahora lo que sorprende a todo el mundo:

originales = ["Cartel feria", "Menu Sole"]
copia = originales              # ¿de verdad es una copia?
copia.append("Logotipo Vidal")
print(originales)               # ['Cartel feria', 'Menu Sole', 'Logotipo Vidal']
print(copia is originales)      # True

Hemos modificado copia y ha cambiado originales. El motivo es el que estudiaste en 04-03 con las referencias: copia = originales no copia la lista, copia la referencia; los dos nombres apuntan al mismo objeto, y a eso se le llama aliasing. Para copiar de verdad hay tres formas equivalentes, que verás bajo el diagrama.

graph LR
    A[originales] --> L["Lista A en memoria<br/>Cartel feria, Menu Sole"]
    B[copia] --> L
    C[de_verdad] --> M["Lista B en memoria<br/>Cartel feria, Menu Sole"]
copia_1 = originales.copy()     # la mas explicita y recomendable
copia_2 = list(originales)      # constructor a partir de la original
copia_3 = originales[:]         # rebanada completa, la clasica
print(len(originales), copia_1 is originales)   # 2 False

Esto tiene una consecuencia directa sobre las funciones, y es nueva respecto a 04-03. Allí vimos que pasar un argumento inmutable no permite a la función tocar la variable de quien llama. Con una lista es al revés:

def anadir_tarea(lista, titulo):
    """Anade un titulo a la lista recibida (la modifica)."""
    lista.append(titulo)

def vaciar_mal(lista):
    lista = []              # reasigna el nombre LOCAL: no se ve fuera

def vaciar_bien(lista):
    lista.clear()           # modifica el objeto: si se ve fuera

agenda = ["Cartel feria"]
anadir_tarea(agenda, "Menu Sole")
print(agenda)       # ['Cartel feria', 'Menu Sole']

La función recibe una referencia a la misma lista, así que sus cambios se ven fuera. Es potentísimo —así añadirá tareas la v0.8— y también peligroso: una función que modifica en silencio una lista ajena provoca sorpresas. Regla práctica: o modifica y lo dice en su nombre (anadir_tarea, ordenar_agenda), o trabaja sobre una copia y devuelve el resultado. Y fíjate en el contraste entre vaciar_mal y vaciar_bien: reasignar el parámetro solo cambia el nombre local, mientras que llamar a un método cambia el objeto que ambos comparten.

  1. Métodos y funciones esenciales

Los métodos se llaman sobre la lista con un punto y casi todos la modifican en el sitio; las funciones integradas se llaman con la lista dentro de los paréntesis y devuelven un valor sin tocarla.

Método Qué hace Devuelve
.append(x) Añade x al final None
.insert(i, x) Inserta x en la posición i None
.extend(otra) Añade todos los elementos de otra None
.remove(x) Borra la primera aparición de x (ValueError si no está) None
.pop(i) Saca el elemento i (el último si se omite) El elemento
.clear() Vacía la lista None
.index(x) Posición de la primera aparición (ValueError si no está) Entero
.count(x) Cuántas veces aparece x Entero
.sort() Ordena en el sitio None
.reverse() Invierte el orden en el sitio None
tareas = ["Cartel feria", "Menu Sole"]
tareas.insert(0, "Revisar correo")          # se cuela en primera posicion
tareas.extend(["Tarjetas Vidal", "Flyer"])  # anade dos de golpe
print(tareas.index("Menu Sole"), tareas.count("Flyer"))     # 2 1
ultima = tareas.pop()                       # saca 'Flyer' y lo devuelve
tareas.remove("Menu Sole")                  # borra por valor, no por posicion
mal = tareas.sort()                         # sort() devuelve None
print(mal)                                  # None -> has perdido la lista

Diferencia decisiva: pop borra por posición y devuelve el elemento; remove borra por valor y no devuelve nada. Y la trampa número uno de las listas es esa última línea: los métodos que modifican devuelven None, así que nunca guardes su resultado. Las funciones integradas, en cambio, devuelven algo nuevo y no tocan la lista:

dias = [3, 5, 2, 8, 1]
print(len(dias), sum(dias), min(dias), max(dias))   # 5 19 1 8
print(f"Media: {sum(dias) / len(dias):.2f}")        # Media: 3.80
print(sorted(dias), dias)   # [1, 2, 3, 5, 8] [3, 5, 2, 8, 1] -> intacta

La regla para elegir: .sort() si quieres que la lista quede ordenada; sorted() si necesitas conservar el orden original. Ambos admiten reverse=True. Cómo ordenan por dentro y cuánto cuesta es materia de Algoritmos de ordenamiento y Eficiencia y notación Big-O; aquí los usamos como herramientas del lenguaje.

  1. Recorrer: for, enumerate, zip e in

El for de 03-02 está hecho para esto. Recorrer la lista directamente —for titulo in titulos:— es la forma preferente en Python: en cada vuelta, titulo toma el valor de un elemento, sin índices, sin len() y sin posibilidad de IndexError. Cuando sí necesitas la posición —numerar un listado, por ejemplo— usa enumerate(), que produce dos valores por vuelta:

for numero, titulo in enumerate(titulos, start=1):
    print(f"{numero}. {titulo}")        # 1. Cartel feria del libro, 2. Menu...

start=1 hace que la numeración que ve el usuario empiece en 1 aunque los índices internos sigan empezando en 0: justo lo que necesita un menú. Cuando tienes dos listas en paralelo, zip() las empareja:

responsables = ["Marta", "Luis", "Nuria"]
for titulo, responsable in zip(titulos, responsables):
    print(f"{titulo:<24}{responsable:>10}")
print("Marta" in responsables)          # True
print("Alba" not in responsables)       # True

zip se detiene con la lista más corta sin avisar: si una tiene un elemento de más, ese elemento desaparece del recorrido. Las dos últimas líneas usan el operador in de 02-02 para comprobar pertenencia. Cómo busca in por dentro, y por qué en una lista larga tarda más que en un conjunto, se ve en Algoritmos de búsqueda y en Diccionarios y conjuntos.

  1. Comprensiones de listas

Este patrón aparece constantemente: crear una lista vacía (dobles = []), recorrer otra secuencia y añadir algo en cada vuelta (dobles.append(d * 2)). Python ofrece una forma condensada de escribir exactamente eso, la comprensión de lista, que se lee de dentro afuera: para cada d de dias, calcula d * 2 y ponlo en la lista.

dobles = [d * 2 for d in dias]              # transformar: [6, 10, 4, 16, 2]
largas = [d for d in dias if d > 3]         # filtrar: [5, 8]
altas = [t.upper() for t in titulos if "feria" in t]    # filtrar y transformar
Forma Significado
[f(x) for x in lista] Transformar todos los elementos
[x for x in lista if cond] Quedarse con algunos
[f(x) for x in lista if cond] Filtrar y transformar

Y ahora la parte que casi nunca se cuenta: cuándo NO usarlas. No la uses si la expresión no cabe cómodamente en una línea, si necesitas condiciones anidadas, si dentro hay que imprimir o pedir datos al usuario, o si encadenas dos o tres for. Algo como [t.upper() if len(t) > 10 else t.lower() for t in titulos if "e" in t] es legal y es ilegible: pide a gritos un bucle normal. El objetivo no era escribir menos, sino leer mejor.

  1. sorted con key, map y filter

En 04-05 quedó pendiente el orden superior sobre colecciones. Ya podemos cerrarlo. sorted(lista, key=funcion) ordena según lo que devuelva funcion aplicada a cada elemento, no según el elemento en sí:

titulos = ["Menu Sole", "Cartel feria del libro", "Flyer", "Logotipo Vidal"]
print(sorted(titulos, key=len))
# ['Flyer', 'Menu Sole', 'Logotipo Vidal', 'Cartel feria del libro']

PRIORIDADES = ("alta", "media", "baja")
prioridades = ["baja", "alta", "media", "alta"]
print(sorted(prioridades, key=lambda p: PRIORIDADES.index(p)))
# ['alta', 'alta', 'media', 'baja']

Con una lambda puedes inventarte cualquier criterio. Ordenar las prioridades alfabéticamente daría alta, baja, media, que no es su orden lógico; en cambio PRIORIDADES.index(p) devuelve 0 para "alta", 1 para "media" y 2 para "baja", y ordenar por ese número da el orden correcto. Es exactamente lo de 04-05: sorted no sabe nada de prioridades, solo de ordenar; el criterio se inyecta desde fuera. Y key=str.lower ordena ignorando mayúsculas, mientras que reverse=True invierte el resultado.

map(funcion, lista) aplica una función a todos los elementos y filter(funcion, lista) se queda con aquellos para los que devuelve True. Ambas devuelven un objeto perezoso que hay que convertir con list():

Objetivo Con comprensión Con map/filter
Transformar [d * 2 for d in dias] list(map(lambda d: d * 2, dias))
Filtrar [d for d in dias if d > 3] list(filter(lambda d: d > 3, dias))

En Python moderno se prefiere la comprensión: más corta, sin list() y más legible. map y filter siguen siendo útiles cuando la función ya existe y tiene nombre (map(str.upper, titulos) es limpísimo), y debes reconocerlos porque abundan en el código ajeno.

  1. TareaFácil v0.8: listas paralelas

La v0.7 guardaba una tarea en seis variables sueltas. La v0.8 guarda muchas tareas en cinco listas paralelas: el elemento de índice i de cada lista se refiere a la misma tarea.

graph LR
    T["titulos"] --> I0["El indice 0 de las cuatro listas<br/>describe la misma tarea"]
    R["responsables"] --> I0
    P["prioridades"] --> I0
    C["completadas"] --> I0

Las funciones de entrada (pedir_texto, pedir_opcion, pedir_entero, confirmar), la de lógica (clasificar_urgencia) y mostrar_menu/pausar se conservan intactas. Cambian las de acción y el main():

# tareafacil.py - Estudio Alba / Version 0.8: muchas tareas en listas paralelas

def mostrar_listado(titulos, responsables, prioridades, completadas):
    """Pinta todas las tareas numeradas desde 1."""
    if len(titulos) == 0:
        print("No hay ninguna tarea registrada.")
        return
    for numero, titulo in enumerate(titulos, start=1):
        i = numero - 1
        marca = "[X]" if completadas[i] else "[ ]"
        print(f"{numero:>2}. {marca} {titulo:<22}{responsables[i]:>8}{prioridades[i]:>7}")
    print("-" * ANCHO)
    print(f"Total: {len(titulos)} tareas, {completadas.count(True)} completadas.")

def registrar_tarea(titulos, responsables, prioridades, dias, completadas):
    """Pide una tarea nueva y la anade al final de las cinco listas."""
    titulos.append(pedir_texto("Titulo        : "))
    responsables.append(pedir_opcion("Responsable   : ", EQUIPO).capitalize())
    prioridades.append(pedir_opcion("Prioridad     : ", PRIORIDADES))
    dias.append(pedir_entero("Dias (1-365)  : ", 1, 365))
    completadas.append(False)
    print(f"Tarea {len(titulos)} registrada correctamente.")

def elegir_indice(titulos):
    """Pide el numero de una tarea y devuelve su indice, o -1 si no hay tareas."""
    if len(titulos) == 0:
        print("No hay ninguna tarea registrada.")
        return -1
    return pedir_entero(f"Numero de tarea (1-{len(titulos)}): ", 1, len(titulos)) - 1

def main():
    """Ejecuta el bucle principal de la aplicacion."""
    titulos, responsables, prioridades, dias, completadas = [], [], [], [], []
    while True:
        mostrar_menu()
        opcion = pedir_opcion("Elige una opcion (1-5): ", OPCIONES)
        if opcion == "5":
            if confirmar("Seguro que quieres salir?"):
                print("Hasta luego. TareaFacil se cierra.")
                break
        elif opcion == "1":
            registrar_tarea(titulos, responsables, prioridades, dias, completadas)
        elif opcion == "2":
            mostrar_listado(titulos, responsables, prioridades, completadas)
        elif opcion == "3":
            i = elegir_indice(titulos)
            if i >= 0:
                prioridades[i] = pedir_opcion("Nueva prioridad: ", PRIORIDADES)
                print(f"Prioridad de '{titulos[i]}' actualizada.")
        elif opcion == "4":
            i = elegir_indice(titulos)
            if i >= 0 and completadas[i]:
                print("Esa tarea ya estaba completada.")
            elif i >= 0:
                completadas[i] = True
                print(f"'{titulos[i]}' completada. Buen trabajo.")
        pausar()

if __name__ == "__main__":
    main()

Lo que ha cambiado y por qué:

  • registrar_tarea ya no devuelve nada: modifica las listas que recibe. Puede hacerlo porque las listas son mutables (sección 5); el nombre y el docstring lo anuncian.
  • elegir_indice traduce el número que ve el usuario al índice interno restando 1. Ese - 1 es la frontera entre «primera tarea» para Marta e «índice 0» para Python, y conviene tenerla en un único sitio.
  • enumerate(..., start=1) numera el listado sin contadores manuales y completadas.count(True) cuenta las hechas sin bucle. Además desaparece la bandera hay_tarea: len(titulos) == 0 responde a lo mismo y encima dice cuántas hay.

Y ahora el problema nuevo que hemos creado. Las cinco listas deben estar sincronizadas siempre: si alguien añade un campo y olvida un append, se desalinean y el responsable de una tarea aparece junto al título de otra. No hay error ni aviso: el programa sigue funcionando y mintiendo. Borrar una tarea exige cinco pop coordinados, y añadir un campo obliga a tocar la firma de todas las funciones. Las cinco listas paralelas son, en realidad, una sola lista de tareas que todavía no sabemos escribir.

Errores Comunes y Consejos

Confundir índice y posición humana. El tercer elemento es lista[2]. Este desfase de uno (off-by-one) es el error más frecuente del oficio: al pintar para el usuario, enumerate(..., start=1); al acceder, resta 1 en un único sitio. Esperar que .sort() devuelva algo. ordenada = lista.sort() deja ordenada valiendo None; si necesitas una lista nueva, sorted(lista). Lo mismo con append, reverse, insert y clear. Y .remove(x) e .index(x) con un valor que no está lanzan ValueError: comprueba antes con if x in lista:.

Creer que copia = lista copia. Copia la referencia. Usa .copy(), list() o [:], y comprueba con is si tienes dudas. Y no borres elementos mientras recorres la lista: si dentro de un for haces lista.remove(x), el recorrido se salta elementos porque las posiciones se desplazan bajo tus pies. La solución segura es construir una lista nueva con los que quieres conservar, normalmente con una comprensión.

Consejo: usa el for directo siempre que puedas. for t in titulos: es más claro y más seguro que for i in range(len(titulos)):. Y mantén las listas homogéneas: una lista contiene «muchas cosas del mismo tipo», no «una cosa repartida en trozos».

Ejercicios

Ejercicio 1: Predecir la salida

Di qué imprime cada print y explica por qué.

dias = [3, 5, 2, 8, 1, 6]
print(dias[2], dias[-1], dias[1:3])
copia = dias
copia.append(4)
print(len(dias))
otra = dias[:]
otra.clear()
print(len(dias), len(otra))
print(dias.sort(), dias[0])

Ejercicio 2: Estadísticas del estudio

Con las listas de abajo, imprime: el número de tareas, el total y la media de días, el título de la tarea más larga, los títulos de prioridad "alta" y los títulos ordenados por días de mayor a menor. Usa comprensiones y sorted(key=...) donde encajen.

titulos = ["Cartel feria", "Menu Sole", "Logotipo Vidal", "Flyer verano"]
responsables = ["Marta", "Luis", "Nuria", "Marta"]
prioridades = ["alta", "media", "alta", "baja"]
dias = [3, 5, 2, 8]

Ejercicio 3: Eliminar una tarea

Escribe eliminar_tarea(titulos, responsables, prioridades, dias, titulo), que borre de las cuatro listas la tarea con ese título sin desalinearlas y avise si no existe. Explica después, en dos frases, por qué esta función resulta incómoda de escribir y de mantener.

Soluciones

Solución 1.

2 6 [5, 2]
7
7 0
None 1

dias[2] es el tercer elemento, dias[-1] el último y dias[1:3] incluye los índices 1 y 2 pero no el 3. copia = dias es aliasing: el append afecta a la única lista que hay y len(dias) pasa de 6 a 7. otra = dias[:] sí es una copia, así que vaciarla no toca la original: 7 y 0. Y .sort() ordena en el sitio devolviendo None; tras ordenar, el primer elemento es el menor, 1.

Solución 2.

print(f"Tareas: {len(titulos)}")
print(f"Total: {sum(dias)} dias, media {sum(dias) / len(dias):.2f}")
print(f"La mas larga: {titulos[dias.index(max(dias))]} ({max(dias)} dias)")

altas = [titulos[i] for i in range(len(titulos)) if prioridades[i] == "alta"]
print(f"Prioridad alta: {', '.join(altas)}")

por_dias = sorted(titulos, key=lambda t: dias[titulos.index(t)], reverse=True)
print(por_dias)     # ['Flyer verano', 'Menu Sole', 'Cartel feria', 'Logotipo Vidal']

Salen 4 tareas, 18 días en total, media 4.50, la más larga «Flyer verano» y dos de prioridad alta. dias.index(max(dias)) da la posición del máximo, y esa posición sirve para leer el título correspondiente: así funcionan las listas paralelas. Fíjate en lo retorcida que queda la última línea: para ordenar por días hay que buscar cada título en su propia lista para averiguar su índice. Con una lista de diccionarios (05-04) será sorted(agenda, key=lambda t: t["dias"]) y se acabó.

Solución 3.

def eliminar_tarea(titulos, responsables, prioridades, dias, titulo):
    """Elimina de las cuatro listas la tarea con el titulo indicado."""
    if titulo not in titulos:
        print(f"No existe ninguna tarea llamada '{titulo}'.")
        return False
    i = titulos.index(titulo)
    titulos.pop(i)
    responsables.pop(i)
    prioridades.pop(i)
    dias.pop(i)
    return True

Es incómoda por dos motivos. Primero, recibe cuatro listas solo para borrar una tarea: si mañana añades un campo, hay que cambiar la firma y todas las llamadas. Segundo, su corrección depende de que los cuatro pop se ejecuten siempre juntos y con el mismo índice; basta olvidar uno para que todas las tareas posteriores queden mezcladas sin ningún error que lo delate.

Conclusión

Una lista guarda muchos valores bajo un solo nombre y resuelve lo que las variables sueltas no podían: contar, recorrer, sumar, ordenar y crecer en tiempo de ejecución. Se accede por índice desde 0, con índices negativos para contar desde el final y IndexError si te pasas; se trocea con rebanadas [inicio:fin:paso], donde el final nunca se incluye. Es mutable, y de ahí el aliasing: copia = lista no copia nada, y para copiar de verdad están .copy(), list() y [:]. Sus métodos modifican en el sitio y devuelven None, mientras que len, sum, min, max y sorted devuelven un valor nuevo. Se recorre con for, con enumerate cuando hace falta la posición y con zip cuando hay varias listas; in comprueba pertenencia. Las comprensiones condensan el patrón «crear, recorrer, añadir» siempre que la línea siga siendo legible. Y con sorted(key=...), map y filter hemos cerrado el capítulo del orden superior de 04-05.

TareaFácil ha llegado a la v0.8 y por fin gestiona el trabajo real del estudio: registra tantas tareas como haga falta, las lista numeradas y permite cambiar la prioridad o completar la enésima. Pero lo ha conseguido con cinco listas paralelas que hay que sincronizar a mano, y ya has visto lo frágil que es: un pop olvidado y el listado empieza a mentir en silencio. El problema no es la lista, es que la estamos usando para algo que no le toca: una lista guarda muchas cosas iguales, y lo que necesitamos es agrupar los campos distintos de una misma tarea bajo un nombre. Eso llega en Diccionarios y conjuntos y se remata en Tuplas y estructuras anidadas. Antes, en Cadenas de caracteres, vamos a mirar de cerca la otra secuencia que llevas usando desde el módulo 2 sin verla como tal —el texto— y a exprimir sus métodos: los títulos, los responsables y los códigos de tarea de Estudio Alba son todos cadenas, y hay mucho que sacarles.

Fundamentos de la Programación

Módulo 1: Introducción a la Programación

Módulo 2: Conceptos Básicos

Módulo 3: Estructuras de Control

Módulo 4: Funciones y Procedimientos

Módulo 5: Estructuras de Datos

Módulo 6: Algoritmos Básicos

Módulo 7: Objetos y Organización del Código

Módulo 8: Buenas Prácticas y Herramientas

Módulo 9: Proyecto Final y Cierre del Curso

© Copyright 2026. Todos los derechos reservados