Con la v0.21 el proyecto está documentado, aguanta los errores, tiene historial y ocho pruebas que lo verifican en centésimas de segundo. Falta lo último, y es lo que separa un programa que funciona de uno del que uno se enorgullece: cómo está escrito. Porque un programa se escribe una vez y se lee decenas: cada vez que vuelves a tocarlo, cada vez que buscas un fallo, cada vez que alguien nuevo entra en el proyecto. En TareaFácil hay funciones en interfaz.py que han crecido hasta las cuarenta líneas, el número 52 aparece suelto en tres sitios, hay nombres heredados de cuando esto era un ejercicio y bloques de código que se repiten con dos palabras cambiadas. Esta lección arregla todo eso: la guía de estilo oficial de Python, las herramientas que la aplican solas, el arte de poner buenos nombres, los olores de código que delatan un problema y un catálogo de refactorizaciones que mejoran el código sin cambiar lo que hace —apoyándote, precisamente, en las pruebas que escribiste en la lección anterior.
Contenido
- PEP 8: la guía de estilo de Python
- Convenciones de nombres
- El Zen de Python
- Herramientas: formateadores y linters
- Poner buenos nombres
- Olores de código
- Qué es refactorizar (y qué no)
- Catálogo de refactorizaciones
- TareaFácil v1.0: revisado, formateado y refactorizado
- Errores comunes y consejos
- Ejercicios
- Conclusión
- PEP 8: la guía de estilo de Python
PEP 8 es el documento oficial que define cómo se escribe Python. No cambia el comportamiento de nada: define el aspecto, y su valor está en que todo el mundo escribe igual, así que cualquier código Python te resulta familiar desde el primer minuto. Estas son sus reglas prácticas:
| Regla | Cómo se hace | Ejemplo |
|---|---|---|
| Indentación | 4 espacios, nunca tabuladores | return total |
| Longitud de línea | Máximo 79 caracteres (muchos proyectos usan 88) | Parte la línea antes de pasarte |
| Líneas en blanco | 2 entre funciones o clases de nivel superior, 1 entre métodos | Separa las ideas |
| Espacios en operadores | Uno a cada lado: a = b + c, no a=b+c |
total = dias * tarifa |
| Espacios en argumentos | Sin espacio alrededor del = de un valor por defecto |
def f(dias=1): |
| Comas | Espacio detrás, nunca delante | f(a, b), no f(a , b) |
| Imports | Uno por línea y arriba del fichero | import json |
| Orden de imports | Estándar → terceros → propios, separados por línea en blanco | Ver ejemplo |
# 1. Biblioteca estandar
import json
import logging
from pathlib import Path
# 2. Paquetes de terceros (aqui no hay: TareaFacil no usa ninguno)
import pytest
# 3. Modulos propios del proyecto
from .modelo import Tarea, PRIORIDADES
from .agenda import AgendaEse orden no es capricho: al mirar la cabecera de un fichero sabes de un vistazo qué depende de fuera y qué es del proyecto. Y una nota importante sobre la longitud de línea: el límite existe para poder leer dos ficheros en paralelo y para que el diff de Git sea legible, no por nostalgia de las pantallas antiguas.
- Convenciones de nombres
El curso lleva usando estas convenciones desde Variables y tipos de datos; ahora les ponemos su nombre oficial:
| Estilo | Se usa para | Ejemplos del proyecto |
|---|---|---|
snake_case |
Variables, funciones y métodos | dias_restantes, resumen_por_responsable |
PascalCase |
Clases | Tarea, TareaRecurrente, Agenda |
MAYUSCULAS |
Constantes | PRIORIDADES, EQUIPO, ANCHO |
_privado |
Uso interno; «no toques esto desde fuera» | _tareas, _normalizar |
modulo.py |
Módulos y paquetes: cortos y en minúscula | modelo.py, almacen.py |
El guion bajo inicial merece una aclaración, porque en Python no impide nada: agenda._tareas es perfectamente accesible. Es un acuerdo entre programadores, no una barrera técnica: significa «esto es un detalle interno, puede cambiar mañana y no deberías depender de ello». Ese acuerdo es justo lo que permitió en 07-03 cambiar por dentro la lista de Agenda sin romper a nadie.
- El Zen de Python
Escribe import this en el intérprete y aparecerán diecinueve aforismos que resumen la filosofía del lenguaje. Estos cinco son los que más te van a servir:
- «Explicit is better than implicit» (explícito mejor que implícito). Por eso
import formatogana afrom formato import *, y por eso una función que devuelveTarea | Nonelo dice en su firma en lugar de dejar que lo descubras al fallar. - «Simple is better than complex» (simple mejor que complejo). Una búsqueda lineal en una lista de 200 tareas es la solución correcta; el índice y el árbol binario son una complejidad que nadie ha pedido.
- «Readability counts» (la legibilidad cuenta). Es la razón de ser de esta lección entera: entre dos soluciones igual de válidas, gana la que se lee mejor.
- «Errors should never pass silently» (los errores nunca deberían pasar en silencio). Exactamente lo que dijimos en 08-02 sobre
except: pass. - «There should be one obvious way to do it» (debería haber una forma obvia de hacerlo). Por eso el proyecto entero usa un solo estilo de docstring y una sola forma de validar.
No son reglas obligatorias sino criterios para desempatar. Cuando dudes entre dos formas de escribir algo, pregúntate cuál de las dos es más explícita, más simple y más legible; casi siempre es la misma.
- Herramientas: formateadores y linters
Lo mejor de todo lo anterior es que casi nada de eso hay que hacerlo a mano. Hay dos familias de herramientas: los formateadores, que reescriben el código para que cumpla el estilo, y los linters, que lo analizan y avisan de problemas.
| Herramienta | Tipo | Qué hace |
|---|---|---|
black |
Formateador | Reformatea el fichero entero. No se configura: no hay discusión posible |
ruff |
Linter (y formateador) | Detecta problemas de estilo y de código. Extremadamente rápido |
flake8 |
Linter | El clásico: comprueba PEP 8 y errores frecuentes |
pylint |
Linter | El más exhaustivo y el más quisquilloso; puntúa tu código |
mypy |
Comprobador de tipos | Verifica las anotaciones de 08-01 sin ejecutar el programa |
pip install black ruff mypy black tareafacil/ # reformatea todo el paquete black --check tareafacil/ # solo dice si haria cambios, sin tocar nada ruff check tareafacil/ # lista los problemas encontrados ruff check --fix tareafacil/ # arregla los que puede arreglar solo mypy tareafacil/ # comprueba la coherencia de los tipos
La diferencia entre los dos tipos es importante: black no opina sobre tu código, solo sobre su aspecto; te quita para siempre las discusiones sobre comillas, espacios y saltos de línea. ruff sí opina: te dirá que has importado algo que no usas, que una variable está definida y nunca se lee o que una función es demasiado compleja. En VS Code se integran en dos clics: instala la extensión de Python, elige black como formateador y activa «Format on Save»; a partir de ahí el estilo deja de ser una preocupación tuya.
- Poner buenos nombres
Es lo único de esta lección que ninguna herramienta puede hacer por ti, y probablemente lo que más influye en la legibilidad. Un buen nombre dice qué es o qué hace, sin abreviaturas crípticas ni información redundante:
| Antes | Después | Por qué |
|---|---|---|
d, x, tmp |
dias, tarea, pendientes |
Una letra solo vale en bucles muy cortos |
lista_de_tareas_del_equipo |
tareas |
Si el contexto ya lo dice, sobra |
procesar(datos) |
normalizar_prioridad(texto) |
«Procesar» no significa nada |
flag |
esta_completada |
Los booleanos se leen como una pregunta |
get_t() |
buscar(titulo) |
El nombre debe decir qué devuelve |
calcular() |
calcular_dias_restantes() |
¿Calcular qué? |
Tres criterios que resumen todo: los booleanos empiezan por un verbo de estado (esta_, hay_, tiene_), las funciones empiezan por un verbo que dice qué hacen (buscar, guardar, normalizar) y las colecciones van en plural (tareas, responsables). Y si un nombre necesita un comentario al lado para entenderse, el nombre está mal.
- Olores de código
Un olor de código (code smell) no es un error: el programa funciona. Es una señal de que algo va a dar problemas pronto. Estos son los siete que encontrarás una y otra vez:
| Olor | Cómo se reconoce | Solución |
|---|---|---|
| Función demasiado larga | No cabe en la pantalla; hace varias cosas | Extraer función |
| Demasiados parámetros | Cinco o más; nadie recuerda el orden | Agrupar en un objeto o usar dataclass |
| Código duplicado | El mismo bloque con dos palabras cambiadas | Extraer una función con parámetros |
| Números y cadenas mágicos | 52, 0.21, "alta" sueltos por ahí |
Sustituir por constante con nombre |
| Anidamiento profundo | Tres o más if metidos unos dentro de otros |
Cláusula de guarda |
| Nombres crípticos | d, tmp2, procesar |
Renombrar |
| Comentario explicativo | Un comentario que aclara un tramo confuso | Extraer ese tramo a una función bien nombrada |
El último es el más sutil y el más útil de aprender. Un comentario del tipo # aqui calculamos el recargo y aplicamos el descuento está señalando, sin querer, que ese bloque es una función que todavía no tiene nombre. Extráelo y llámalo aplicar_recargo_y_descuento(): el comentario desaparece porque ya no hace falta, y de paso el trozo se vuelve probable de forma aislada.
- Qué es refactorizar (y qué no)
Refactorizar es cambiar la estructura interna del código sin cambiar en absoluto lo que hace. Esa segunda parte es la definición completa, y de ella salen dos consecuencias que conviene tener muy claras:
- No es refactorizar añadir una funcionalidad, arreglar un fallo ni mejorar el rendimiento. Todo eso cambia el comportamiento; refactorizar, no.
- Nunca se hacen las dos cosas a la vez. Si refactorizas y añades una función en el mismo commit y algo se rompe, no sabrás qué lo rompió. Primero una cosa, después la otra, cada una con su commit (08-03).
Y de ahí sale la regla de oro: antes de refactorizar hay que tener pruebas. Las pruebas de 08-04 son las que responden a la única pregunta que importa mientras reorganizas el código —«¿sigue haciendo exactamente lo mismo?»— y las que convierten la operación en algo seguro. El ciclo es siempre este: pruebas en verde, un cambio pequeño, ejecutar las pruebas, commit; y vuelta a empezar. Si en algún momento se ponen en rojo, sabes con total certeza qué lo ha provocado, porque acabas de hacer un solo cambio.
- Catálogo de refactorizaciones
Estas seis resuelven la inmensa mayoría de los casos. Extraer función parte un bloque largo en piezas con nombre:
# ANTES: un bloque que hace tres cosas
def mostrar_ficha(tarea):
print("=" * 52)
print(f"{tarea.titulo.upper():^52}")
print("=" * 52)
barra = "#" * int(tarea.progreso() / 10) + "-" * (10 - int(tarea.progreso() / 10))
print(f"Avance: [{barra}] {tarea.progreso():.0f}%")
# DESPUES: cada idea, con su nombre
def cabecera(texto: str) -> str:
"""Devuelve el texto centrado entre lineas de separacion."""
return f"{'=' * ANCHO}\n{texto.upper():^{ANCHO}}\n{'=' * ANCHO}"
def barra_progreso(porcentaje: float) -> str:
"""Devuelve una barra de 10 casillas para ese porcentaje."""
llenas = int(porcentaje / 10)
return "#" * llenas + "-" * (10 - llenas)Extraer variable explicativa convierte una expresión ilegible en algo que se entiende solo, y sustituir el número mágico por una constante hace lo mismo con los valores sueltos:
# ANTES
if tarea.dias > 20 and tarea.prioridad == "alta" and not tarea.completada:
total *= 0.9
# DESPUES
DIAS_PROYECTO_LARGO = 20
DESCUENTO_LARGO = 0.9
es_proyecto_largo_urgente = (tarea.dias > DIAS_PROYECTO_LARGO
and tarea.prioridad == "alta"
and not tarea.completada)
if es_proyecto_largo_urgente:
total *= DESCUENTO_LARGOInvertir la condición y usar una cláusula de guarda aplana el anidamiento profundo, que es el olor que más cuesta leer:
# ANTES: tres niveles de anidamiento
def guardar(agenda, ruta):
if agenda is not None:
if len(agenda) > 0:
if ruta.parent.exists():
escribir_json(agenda, ruta)
# DESPUES: los casos imposibles se descartan primero y se sale
def guardar(agenda, ruta):
if agenda is None or len(agenda) == 0:
return
if not ruta.parent.exists():
raise FileNotFoundError(f"No existe la carpeta {ruta.parent}")
escribir_json(agenda, ruta)Unificar duplicados y sustituir una cadena de if por un diccionario de despacho cierran el catálogo. Lo segundo es especialmente rentable en los menús:
# ANTES: una cadena que crece con cada opcion nueva
if opcion == "1":
crear_tarea(agenda)
elif opcion == "2":
listar_tareas(agenda)
elif opcion == "3":
completar_tarea(agenda)
# DESPUES: un diccionario de despacho (funciones como valores, 04-05)
ACCIONES = {"1": crear_tarea, "2": listar_tareas, "3": completar_tarea}
accion = ACCIONES.get(opcion)
if accion is not None:
accion(agenda)Fíjate en lo que ha ganado la segunda versión: añadir una opción nueva es una línea en el diccionario en lugar de dos en la cadena, el conjunto de opciones válidas está en un único sitio y ACCIONES.keys() sirve directamente para validar la entrada del usuario. Es la misma idea de las funciones como valores de 04-05, aplicada al problema real.
- TareaFácil v1.0: revisado, formateado y refactorizado
Primero, las herramientas, con las pruebas en verde antes de empezar:
$ pytest -q 8 passed in 0.05s $ black tareafacil/ tests/ reformatted tareafacil/interfaz.py reformatted tareafacil/agenda.py All done! 2 files reformatted, 4 files left unchanged. $ ruff check tareafacil/ tareafacil/interfaz.py:12:1: F401 [*] `csv` imported but unused tareafacil/interfaz.py:48:5: C901 `gestionar_opcion` is too complex (14) tareafacil/almacen.py:7:1: E501 Line too long (92 > 88) Found 3 errors (1 fixable with `--fix`).
ruff ha señalado exactamente las dos funciones que sabíamos que estaban mal: gestionar_opcion, con su cadena de catorce elif, y mostrar_ficha, con el bloque de formato. La primera se arregla con el diccionario de despacho y la segunda extrayendo cabecera() y barra_progreso(), tal y como acabas de ver. Después de cada cambio, la comprobación que lo justifica todo:
$ pytest -q 8 passed in 0.05s $ ruff check tareafacil/ All checks passed! $ git commit -am "Refactorizar interfaz.py: despacho por diccionario y extraccion" $ git tag -a v1.0 -m "TareaFacil 1.0: documentado, robusto, versionado y probado"
Las ocho pruebas siguen en verde sin haber tocado una sola de ellas, y eso es la demostración de que la refactorización fue una refactorización de verdad: el comportamiento es idéntico. El proyecto llega así a la v1.0, el número que según el versionado semántico de 08-01 significa «esto ya es estable». Mira de dónde viene:
| Versión | Qué era TareaFácil |
|---|---|
| v0.1 (mód. 2) | Cuatro variables y un print con f-strings |
| v0.4 (mód. 3) | Un menú en un bucle while con condicionales |
| v0.8 (mód. 4) | Funciones, un main() y la lógica separada de la interfaz |
| v0.11 (mód. 5) | Listas y diccionarios, con guardado en JSON y CSV |
| v0.13 (mód. 6) | Búsquedas, ordenaciones y conciencia del coste |
| v0.17 (mód. 7) | Clases Tarea y Agenda en el paquete tareafacil/ |
| v0.18 | Documentado: docstrings, anotaciones de tipo y README.md |
| v0.19 | Robusto: try/except, TareaInvalida y logging |
| v0.20 | Versionado: repositorio Git con historial y .gitignore |
| v0.21 | Probado: carpeta tests/ con ocho pruebas en verde |
| v1.0 | Formateado con black, revisado con ruff y refactorizado |
Errores Comunes y Consejos
- Discutir sobre estilo. Es tiempo perdido: instala
black, actívalo al guardar y dedica esa energía a lo que sí importa. - Refactorizar sin pruebas. Es reescribir a ciegas. Primero las pruebas en verde, después el cambio.
- Refactorizar y cambiar el comportamiento en el mismo commit. Si algo se rompe, no sabrás qué fue. Un commit, una intención.
- La refactorización gigante. Cambios pequeños con las pruebas ejecutadas entre uno y otro; nunca una reescritura de tres días.
- Confundir «corto» con «legible». Una línea de código que hay que leer tres veces no es mejor que tres líneas claras.
- Hacer caso ciego al linter. Avisa, no manda. Si tienes una razón para dejar algo como está, déjalo (y anota el porqué en un comentario).
- Consejo: aplica la regla del campamento. Deja cada fichero que tocas un poco mejor que como lo encontraste: un nombre, una constante, una función extraída. En unos meses el proyecto entero está limpio sin haber parado nunca a limpiarlo.
Ejercicios
Ejercicio 1: Estilo y nombres
Reescribe este fragmento aplicando PEP 8, las convenciones de nombres y las constantes que hagan falta:
import json,csv
def PROC(l,f):
r=[]
for x in l:
if x['p']=='alta' and x['d']>20:r.append(x)
return rEjercicio 2: Detectar olores
Enumera los olores de código de esta función, di qué refactorización corresponde a cada uno y reescríbela:
def informe(tareas, tipo):
if tipo == "corto":
for t in tareas:
print(f"{t.titulo[:30]:<30} {t.responsable:<10}")
elif tipo == "largo":
for t in tareas:
print(f"{t.titulo[:30]:<30} {t.responsable:<10} {t.dias:>3} dias")
elif tipo == "csv":
for t in tareas:
print(f"{t.titulo},{t.responsable},{t.dias}")Ejercicio 3: Cláusula de guarda y despacho
Refactoriza esta función usando una cláusula de guarda y un diccionario de despacho, y explica por qué el resultado es más fácil de ampliar:
def aplicar_accion(tarea, accion):
if tarea is not None:
if not tarea.completada:
if accion == "completar":
tarea.completar()
elif accion == "posponer":
tarea.dias += 1
elif accion == "urgente":
tarea.prioridad = "alta"Soluciones
Solución 1.
import csv
import json
DIAS_PROYECTO_LARGO = 20
PRIORIDAD_URGENTE = "alta"
def filtrar_urgentes_largas(tareas: list[dict]) -> list[dict]:
"""Devuelve las tareas de prioridad alta que superan los 20 dias."""
return [
tarea
for tarea in tareas
if tarea["prioridad"] == PRIORIDAD_URGENTE
and tarea["dias"] > DIAS_PROYECTO_LARGO
]Los cambios son siete: indentación de 4 espacios, un import por línea y en orden alfabético, nombre de función en snake_case y descriptivo (PROC no dice nada y además parecía una clase), variables con nombre completo, las claves 'p' y 'd' legibles, los números mágicos convertidos en constantes y el if de una línea desplegado. Ah, y el parámetro f no se usaba: ruff lo habría detectado en el acto.
Solución 2.
Hay tres olores: código duplicado (el bucle se repite tres veces idéntico), una cadena de if sobre un valor (candidata a diccionario de despacho) y cadenas mágicas ("corto", "largo", "csv" sueltas). La refactorización que corresponde es extraer el formato de una línea a funciones y despachar por diccionario:
FORMATOS = {
"corto": lambda t: f"{t.titulo[:30]:<30} {t.responsable:<10}",
"largo": lambda t: f"{t.titulo[:30]:<30} {t.responsable:<10} {t.dias:>3} dias",
"csv": lambda t: f"{t.titulo},{t.responsable},{t.dias}",
}
def informe(tareas: list[Tarea], tipo: str = "corto") -> None:
"""Imprime el informe de tareas en el formato indicado."""
formatear = FORMATOS.get(tipo)
if formatear is None:
raise ValueError(f"Formato no valido: {tipo!r}. Usa {list(FORMATOS)}.")
for tarea in tareas:
print(formatear(tarea))El bucle aparece una sola vez y lo único que varía —el formato de la línea— vive en el diccionario. Añadir un formato nuevo es ahora una línea, los formatos válidos están en un único sitio y el mensaje de error los enumera solo. Es exactamente la idea de las funciones como valores de 04-05 puesta a trabajar.
Solución 3.
def aplicar_accion(tarea: Tarea | None, accion: str) -> None:
"""Aplica una accion a una tarea pendiente."""
if tarea is None or tarea.completada: # clausula de guarda
return
ACCIONES[accion](tarea)
ACCIONES = {
"completar": lambda t: t.completar(),
"posponer": lambda t: setattr(t, "dias", t.dias + 1),
"urgente": lambda t: setattr(t, "prioridad", "alta"),
}La cláusula de guarda descarta al principio los casos en los que no hay nada que hacer, y el cuerpo real de la función queda sin anidamiento: se lee de arriba abajo sin sostener condiciones en la cabeza. Y ampliar es trivial: una acción nueva es una entrada en ACCIONES, sin tocar la función. En un proyecto real conviene ir un paso más allá y sustituir esas lambda con setattr por métodos de Tarea (posponer(), marcar_urgente()), porque el comportamiento de una tarea pertenece a la clase Tarea: es la lección de 07-02 aplicada aquí.
Conclusión
El código se lee muchas más veces de las que se escribe, y por eso el estilo no es cosmética. PEP 8 fija lo básico —4 espacios de indentación, líneas de menos de 79 (u 88) caracteres, líneas en blanco que separan ideas, espacios alrededor de los operadores e imports ordenados en estándar, terceros y propios— y las convenciones de nombres ponen nombre oficial a lo que el curso venía haciendo desde 02-01: snake_case para variables y funciones, PascalCase para clases, MAYUSCULAS para constantes y _privado como acuerdo entre programadores que Python no impone. El Zen de Python (import this) aporta los criterios para desempatar: explícito mejor que implícito, simple mejor que complejo, la legibilidad cuenta, los errores nunca deberían pasar en silencio y debería haber una forma obvia de hacer las cosas. Casi todo eso lo aplican solas las herramientas: black reformatea sin discusión, ruff, flake8 y pylint detectan problemas, mypy comprueba las anotaciones de 08-01, y VS Code lo ejecuta al guardar. Lo único que no puede automatizarse son los nombres: verbos para las funciones, plural para las colecciones, esta_/hay_ para los booleanos y nunca una abreviatura que necesite un comentario. Los olores de código —función demasiado larga, demasiados parámetros, duplicación, números y cadenas mágicos, anidamiento profundo, nombres crípticos y el comentario que explica un tramo confuso— señalan dónde actuar. Y refactorizar es mejorar la estructura sin cambiar el comportamiento, jamás mezclado con otra cosa en el mismo commit y siempre con pruebas en verde antes de empezar: extraer función, extraer variable explicativa, sustituir número mágico por constante, invertir la condición con una cláusula de guarda, unificar duplicados y sustituir la cadena de if por un diccionario de despacho.
TareaFácil es la v1.0: formateado con black, revisado con ruff, con interfaz.py refactorizada y con las mismas ocho pruebas en verde, que es la prueba de que nada cambió por fuera. Y con esto se cierra el módulo 8 y, de hecho, todo el recorrido técnico del curso. Empezaste sin saber qué era una variable y ahora tienes el lenguaje (tipos, operadores, entrada y salida), las estructuras de control, las funciones, las estructuras de datos con su persistencia en ficheros, los algoritmos de búsqueda y ordenación con su coste, la programación orientada a objetos con módulos y paquetes, y las cinco herramientas profesionales de este módulo: documentación, depuración y manejo de errores, control de versiones, pruebas automatizadas y refactorización. Eso es exactamente el equipaje con el que trabaja un programador.
Solo queda una cosa por hacer, y es la más importante de todas: construir algo entero desde cero, tú solo. Hasta ahora cada pieza de TareaFácil venía propuesta; en el módulo 9 el proyecto lo eliges, lo diseñas, lo planificas, lo implementas, lo pruebas y lo presentas. Empezamos en Definición del proyecto, decidiendo qué merece la pena construir y —tan importante como eso— dónde poner el límite para terminarlo.
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
