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
- De las variables sueltas a la lista
- Arreglo clásico y lista de Python
- Crear listas, medirlas e indexarlas
- Rebanadas:
[inicio:fin:paso] - Mutabilidad, aliasing y copias
- Métodos y funciones esenciales
- Recorrer:
for,enumerate,zipein - Comprensiones de listas
sortedconkey,mapyfilter- TareaFácil v0.8: listas paralelas
- Errores comunes y consejos
- Ejercicios
- Conclusión
- 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.
- 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.
- 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 0La 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 rangeUna 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.
- Rebanadas:
[inicio:fin:paso]
[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.
- 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) # TrueHemos 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 FalseEsto 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.
- 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 listaDiferencia 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] -> intactaLa 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.
- Recorrer:
for, enumerate, zip e in
for, enumerate, zip e inEl 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) # Truezip 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.
- 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.
sorted con key, map y filter
sorted con key, map y filterEn 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.
- 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_tareaya 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_indicetraduce el número que ve el usuario al índice interno restando 1. Ese- 1es 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 ycompletadas.count(True)cuenta las hechas sin bucle. Además desaparece la banderahay_tarea:len(titulos) == 0responde 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.
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 TrueEs 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
- ¿Qué es la programación?
- Historia de la programación
- Lenguajes de programación
- Entornos de desarrollo
- Del problema al algoritmo
Módulo 2: Conceptos Básicos
- Variables y tipos de datos
- Operadores y expresiones
- Entrada y salida de datos
- Conversión de tipos y validación de datos
Módulo 3: Estructuras de Control
Módulo 4: Funciones y Procedimientos
- Definición y uso de funciones
- Parámetros y retorno de valores
- Ámbito de variables
- Descomponer un programa en funciones
- Funciones como valores: lambda y orden superior
Módulo 5: Estructuras de Datos
- Listas y arreglos
- Cadenas de caracteres
- Diccionarios y conjuntos
- Tuplas y estructuras anidadas
- Guardar datos en archivos: texto, CSV y JSON
Módulo 6: Algoritmos Básicos
Módulo 7: Objetos y Organización del Código
- De los datos a los objetos: clases e instancias
- Atributos, métodos y constructor
- Colecciones de objetos
- Módulos, paquetes e importaciones
Módulo 8: Buenas Prácticas y Herramientas
- Documentación y comentarios
- Depuración y manejo de errores
- Control de versiones
- Pruebas automatizadas
- Estilo, legibilidad y refactorización
