Documentar código no es escribir lo que el código hace — eso ya lo dice el código. Es escribir lo que el código no puede decir por sí mismo: por qué existe, qué decisión de diseño hay detrás, qué trampa estás evitando.
Python te da dos herramientas para esto, y el intérprete las trata de manera completamente distinta.
Un comentario empieza con # y el intérprete lo ignora por completo. Desaparece en el momento del parsing. No hay objeto, no hay memoria, no hay rastro en tiempo de ejecución. Es texto para humanos, punto.
Un docstring es una cadena de texto literal — normalmente con triple comilla — que aparece como la primera expresión de un módulo, función o clase. Aquí el intérprete sí hace algo: toma ese string y lo guarda en el atributo __doc__ del objeto que acabas de definir. Eso significa que está vivo en tiempo de ejecución, que help() puede mostrarlo, que las herramientas de documentación como Sphinx pueden extraerlo, y que tu propio código puede consultarlo programáticamente.
La diferencia no es estética. Cuando escribes un comentario estás hablando con quien lee el fuente. Cuando escribes un docstring estás hablando también con el intérprete, con las herramientas, con quien use tu función sin ver su implementación.
¿Cuándo usar cada uno? Usa # para explicar una decisión local — por qué ese número mágico, por qué ese orden, qué bug estás evitando. Usa docstrings para describir el contrato público de una función, clase o módulo: qué recibe, qué devuelve, qué puede lanzar. Si alguien va a llamar tu función sin leer su cuerpo, el docstring es lo único que tiene.
Lo que se rompe si lo haces mal: comentarios que describen qué hace el código (“sumamos los elementos”) se dessincronizan con el código y mienten. Un docstring mal colocado — después de una asignación, por ejemplo — no se asigna a __doc__ y simplemente flota como un string huérfano que nadie va a leer.
# sensor_stats.py
"""
Módulo para calcular estadísticas básicas de lecturas de sensores.
Asume que los valores ya vienen limpios (sin None ni NaN).
"""
def average(readings: list[float]) -> float:
"""
Calcula la media aritmética de una lista de lecturas.
Args:
readings: Lista de valores numéricos. No puede estar vacía.
Returns:
La media como float.
Raises:
ValueError: Si la lista está vacía.
"""
if not readings:
raise ValueError("No se puede calcular la media de una lista vacía")
return sum(readings) / len(readings)
def threshold_count(readings: list[float], limit: float) -> int:
"""
Cuenta cuántas lecturas superan el umbral dado.
Args:
readings: Lista de valores numéricos.
limit: Valor umbral (exclusivo).
Returns:
Número de lecturas estrictamente mayores que limit.
"""
# Usamos comparación estricta porque el umbral en el protocolo del sensor
# representa el límite aceptable, no el valor de alarma.
return sum(1 for r in readings if r > limit)
if __name__ == "__main__":
data = [18.2, 21.5, 19.8, 23.1, 20.0]
print(average(data)) # 20.52
print(threshold_count(data, 20.0)) # 2
# Introspección: el intérprete guardó los docstrings como atributos
print(average.__doc__)
print(threshold_count.__doc__)
Qué está pasando aquí
El módulo arranca con un docstring de nivel de módulo. Fíjate que explica una asunción — que los datos vienen limpios. Eso es información que no está en ninguna línea de código pero que cualquiera que use este módulo necesita saber.
Cada función tiene su docstring justo después de def, antes de cualquier otra línea. Ese es el único lugar donde Python lo asigna a __doc__. Seguimos la convención de PEP 257: primera línea como resumen breve en imperativo (“Calcula…”, “Cuenta…”), luego una línea en blanco, luego los detalles. PEP 257 no es arbitrario — estandariza el formato para que herramientas como help(), pydoc y Sphinx sepan cómo parsear la estructura.
El comentario en threshold_count es el tipo correcto de comentario: explica por qué la comparación es estricta, una decisión de dominio que no se puede inferir del código. Si mañana alguien cambia > por >= sin leer ese comentario, introduce un bug silencioso. El comentario es la defensa.
En el bloque __main__ hay dos comentarios cortos junto a los print. Esos están bien para código de ejemplo o scripts rápidos, pero en código de producción esos resultados esperados pertenecen a tests, no a comentarios.
Al final, average.__doc__ y threshold_count.__doc__ imprimen los docstrings tal como los escribiste. Eso demuestra que no son texto muerto — son datos accesibles en tiempo de ejecución, lo que hace posible cosas como help(average) desde el REPL o la generación automática de documentación HTML sin tocar el código fuente.
Errores que debes conocer
Error: Colocar el docstring después de una asignación o de otra sentencia dentro de la función — el intérprete no lo reconoce como docstring y __doc__ queda como None.
# ❌ Wrong
def process(data):
result = []
"""Procesa los datos y devuelve una lista filtrada.""" # string huérfano
return [x for x in data if x > 0]
# ✅ Right
def process(data):
"""Procesa los datos y devuelve una lista filtrada."""
result = []
return [x for x in data if x > 0]
El docstring debe ser la primera expresión del cuerpo, sin nada delante.
Error: Escribir comentarios que describen el código en lugar de la razón detrás de él — se desactualizan y terminan mintiendo.
# ❌ Wrong # Dividimos total entre count average = total / count # ✅ Right # Evitamos round() aquí porque los sensores reportan con precisión de 0.1 # y redondear introduce deriva acumulada en cálculos posteriores. average = total / count
El primer comentario dice exactamente lo que ya dice el código. El segundo aporta algo que el código no puede decir.
N° 15