El Zen de Python: criterio de diseño, no póster de oficina

Cuando escribes import this en el intérprete, Python te muestra 19 aforismos redactados por Tim Peters en 1999. La mayoría de la gente los lee una vez, asiente, y sigue su vida. Eso es un error, porque esos aforismos son la explicación más honesta de por qué Python está diseñado como está, y conocerlos te da un criterio para tomar decisiones de diseño cuando no hay una respuesta obvia.

El Zen no es filosofía decorativa. Es el modelo mental que usaron Guido van Rossum y los core developers para resolver miles de discusiones sobre la stdlib, la sintaxis y las convenciones del lenguaje. Cuando entiendes qué significa cada línea en términos de código real, dejas de adivinar si tu API está bien diseñada y empiezas a razonarlo.

Vamos a diseccionar los cuatro aforismos que más impacto tienen en el código que escribes día a día.


“Explicit is better than implicit” es probablemente el más citado y el más malentendido. No significa “escribe código verboso”. Significa que cuando tu función hace algo, ese algo debe ser visible en la firma o en el nombre — no debe ocurrir como efecto secundario silencioso. Si una función modifica su argumento, el lector debe poder saberlo sin leer el cuerpo entero. Si retorna algo, debe ser obvio qué retorna. La implicitez que Python rechaza es la que obliga al lector a mantener estado mental oculto para entender el código.

“There should be one obvious way to do it” explica por qué la stdlib tiene str.join() en lugar de tres variantes con nombres distintos, por qué los list comprehensions existen aunque podrías usar map y filter, y por qué Python resiste activamente la proliferación de aliases. Cuando diseñas una API y te encuentras añadiendo un segundo método que hace “casi lo mismo pero con otra interfaz”, este aforismo te está diciendo que pares y reconsideres.

“Errors should never pass silently” es donde Python se separa filosóficamente de lenguajes que priorizan la continuidad sobre la corrección. C te da -1 o NULL cuando algo falla y confía en que compruebes el valor de retorno. JavaScript convierte tipos en silencio y sigue adelante. Python lanza una excepción. Esto no es rigidez — es honestidad. Un error que pasa silenciosamente no desaparece; se convierte en un bug que aparece tres capas más tarde en un contexto completamente diferente y que es tres veces más difícil de depurar.

El cuarto aforismo crucial es “Readability counts”, que justifica decisiones sintácticas que a veces parecen caprichosas: el self explícito en los métodos, la indentación obligatoria, el as en los import. No son accidentes — son consecuencias de priorizar que el código se lea con facilidad, porque el código se lee mucho más veces de las que se escribe.

Con ese marco en la cabeza, mira este ejemplo:

from datetime import date
from typing import Optional


def days_until_event(
    event_date: date,
    reference_date: Optional[date] = None,
) -> int:
    """
    Devuelve los días que faltan para event_date desde reference_date.
    Si reference_date es None, usa la fecha de hoy.
    Retorna un número negativo si el evento ya pasó.
    """
    if reference_date is None:
        reference_date = date.today()  # explícito: el default se documenta Y se ve en el cuerpo

    return (event_date - reference_date).days


def send_reminders(events: list[tuple[str, date]]) -> None:
    for name, event_date in events:
        days_left = days_until_event(event_date)

        if days_left < 0:
            # Error explícito: no ignoramos eventos pasados en silencio
            raise ValueError(
                f"El evento '{name}' ya pasó hace {abs(days_left)} días. "
                "Elimínalo de la lista antes de enviar recordatorios."
            )

        if days_left == 0:
            print(f"HOY: {name}")
        else:
            print(f"Faltan {days_left} días para: {name}")


if __name__ == "__main__":
    upcoming = [
        ("PyCon", date(2025, 9, 15)),
        ("Code review semanal", date(2025, 8, 1)),
    ]
    send_reminders(upcoming)

Fíjate en las decisiones concretas que conectan con el Zen.

days_until_event recibe reference_date como parámetro explícito en lugar de llamar a date.today() directamente en la firma con default=date.today(). Eso sería un bug clásico en Python — los defaults de función se evalúan una sola vez en el momento de la definición, no en cada llamada, así que todos los llamadores compartirían la misma fecha fija. Pero además de evitar el bug, declarar el parámetro explícitamente hace que la función sea testeable sin parches: puedes pasarle cualquier fecha de referencia sin mockear nada. “Explicit is better than implicit” no es solo estética — aquí elimina una clase entera de errores.

El docstring documenta el comportamiento para valores negativos. Esto cumple “Errors should never pass silently” en su forma más suave: no ocultamos que el resultado puede ser negativo, lo declaramos. Un diseño alternativo sería retornar 0 cuando el evento ya pasó — eso sería silenciar el error. El código que llama a esta función tomaría decisiones incorrectas sin ninguna señal de que algo raro ocurrió.

En send_reminders, cuando encontramos un evento pasado lanzamos ValueError inmediatamente con un mensaje que explica qué pasó y qué hacer al respecto. El mensaje no es un lujo — es el aforismo en acción. Los errores que pasan silenciosamente no se van; se manifiestan como comportamiento extraño en otro lugar, y entonces tienes que reconstruir toda la cadena de llamadas para entender qué ocurrió originalmente.

La estructura de la lista de tuplas (str, date) también es deliberada. Podría ser una lista de diccionarios, o una lista de objetos, o dos listas paralelas. Pero con “one obvious way”, la pregunta es: ¿cuál estructura hace que el código que la consume sea más legible sin requerir documentación extra? La tupla nombrada por desestructuración en el for lo dice todo: name, event_date. No hay ambigüedad sobre qué es cada elemento.

Errores que debes conocer

Error: usar un objeto mutable como valor por defecto en una función, confiando en que Python lo reinicializa en cada llamada.

# ❌ Wrong
def add_event(name: str, events: list = []) -> list:
    events.append(name)
    return events

print(add_event("PyCon"))    # ['PyCon']
print(add_event("DjangoCon")) # ['PyCon', 'DjangoCon'] ← sorpresa

# ✅ Right
def add_event(name: str, events: list | None = None) -> list:
    if events is None:
        events = []
    events.append(name)
    return events

El default [] se crea una vez cuando Python carga el módulo; todas las llamadas sin argumento comparten la misma lista. El patrón None como sentinel fuerza a crear una lista nueva en cada invocación y hace el comportamiento completamente explícito.

Error: suprimir excepciones con un except vacío o demasiado amplio porque “no queremos que el programa se rompa”.

# ❌ Wrong
def load_config(path: str) -> dict:
    try:
        with open(path) as f:
            return eval(f.read())  # también un problema de seguridad, pero lo ignoramos aquí
    except:
        return {}  # el archivo no existía... o lanzó un SyntaxError... o lo que sea

# ✅ Right
import json

def load_config(path: str) -> dict:
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError:
        return {}  # este caso específico es recuperable y lo documentamos con el tipo de excepción

Capturar Exception (o peor, la clase base BaseException con except:) silencia errores que deberías conocer: errores de sintaxis en el JSON, problemas de permisos, bugs en tu propio código. Captura solo lo que puedes manejar con intención.

6

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio