English · Español
Lab 00 — Entorno y utilidades¶
Objetivo: entregar
src/utils/seeding.pyysrc/utils/logging.py— dos módulos diminutos que importa cada fase posterior.Tiempo estimado: 60–90 minutos.
Prerrequisitos: el entorno de la Fase 0 está montado (
uv,just,ruff,mypy,pytest).
Lo que produces¶
src/utils/seeding.py—seed_everything(seed: int) -> int. Siembra elrandomde Python, eldefault_rngde NumPy yPYTHONHASHSEED. Devuelve la semilla para registro en logs.src/utils/logging.py—get_logger(name: str). Devuelve un logger destructlogconfigurado para emitir JSON a stdout, con un campo de contextophaseque quien llama puede fijar.tests/test_seeding.py— verifica el determinismo: dos llamadas aseed_everything(42)seguidas denp.random.default_rng().random(5)producen arrays idénticos.tests/test_logging.py— verifica que el logger emite una línea JSON con los campos esperados.experiments/06-environment-check/manifest.json+README.md— un pequeño smoke test que importa ambas utilidades, registra un mensaje y siembra un RNG.
TODOs¶
Bloque A — seed_everything¶
- Firma de la función:
def seed_everything(seed: int) -> int. Con anotaciones de tipo. Docstring. - Siembra
random.seed(seed)de Python. - Fija
os.environ["PYTHONHASHSEED"] = str(seed)— nota que esto solo afecta a procesos hijo; documenta el caveat. - NO llames a
np.random.seed(seed)(estado global — ver theory/01). En su lugar, devuelve la semilla y deja que quien llama hagarng = np.random.default_rng(seed_everything(42)). - Decide y documenta: ¿debería sembrarse también
torch? La Fase 6 no importa torch ensrc/; difiere a los tests de la Fase 8 que lo necesitan. Manténseeding.pylibre de torch. - Registra un evento de
structlogcuando se llame:log.info("seed_set", seed=seed).
Bloque B — get_logger¶
- Firma de la función:
def get_logger(name: str). - Configura
structloguna vez en tiempo de importación del módulo (idempotente). - Processors: timestamp (ISO 8601, UTC), nivel de log, el mensaje en sí, JSONRenderer.
- Proporciona un helper
bind_phase(phase: str)que devuelve un logger pre-bindeado con un campophase. - Testea que llamar a
get_logger("foo").info("bar", x=1)emite JSON válido en stdout que contieneevent="bar",x=1, un timestamp ylogger="foo".
Bloque C — tests¶
-
tests/test_seeding.py: - Test 1:
seed_everything(42)devuelve 42. - Test 2: Tras dos llamadas a
seed_everything(42), dos llamadas anp.random.default_rng(42).random(5)producen arrays idénticos. - Test 3: Tras
seed_everything(42),random.random()es determinista. -
tests/test_logging.py: - Test 1:
get_loggerdevuelve un objeto con métodosinfo,warning,error,debug. - Test 2: Capturando stdout, llamar a
log.info("event_name", key="value")emite JSON que contiene"event": "event_name"y"key": "value". Usa la fixturecapsys. - Test 3:
bind_phase("phase-06").info("foo")incluye"phase": "phase-06"en el JSON.
Bloque D — smoke test¶
- Crea
experiments/06-environment-check/con: check.py— importa ambas utilidades, llama aseed_everything(42), registra un mensaje info con la semilla.manifest.json—{seed, versions, config, hardware}segúnLYNX_CORTEX.md§5.README.md(1 párrafo) — qué verifica este experimento.
Restricciones¶
mypy --strictdebe pasar. Todas las funciones tipadas, sinAnyimplícito.ruffdebe pasar. Longitud de línea 100 (el default del repo — confirma enpyproject.toml).pytestdebe pasar. Todos los tests en verde.- Sin
print. Usalog.infoincluso en el smoke test. - Sin
np.random.seed. Usanp.random.default_rng(seed)solamente. - Idempotencia.
seed_everything(42); seed_everything(42)debe producir el mismo comportamiento aguas abajo que una sola llamada. Lo mismo paraget_logger("foo"); get_logger("foo").
Condiciones de parada¶
Hecho cuando:
- Ambos archivos de utilidad existen, ambos pasan
mypy --strictyruff. - Ambos archivos de test existen, todos los tests pasan.
- El smoke experiment se ejecuta sin error y emite una línea de log JSON.
git diffno muestra sentenciasprinten ningún sitio de tus archivos nuevos.
Escollos¶
structlogno configurado antes del primer uso. Si llamas alog.info(...)antes de que corrastructlog.configure(...), obtienes la cadena de processors por defecto, no la tuya. Configura en tiempo de importación del módulologging.py, y asegúrate de que los tests importenlogging.pyantes de ejercitar loggers.PYTHONHASHSEEDfijado después del arranque de Python. Fijaros.environ["PYTHONHASHSEED"]desde dentro de Python no cambia retroactivamente la hash seed del proceso actual (se decidió en el arranque del intérprete). Solo afecta a procesos hijo lanzados víasubprocess. Documéntalo en el docstring; no pretendas que hace al proceso padre determinista.- Captura de stdout en tests.
capsys.readouterr().outdevuelve un string. Para líneas JSON, puede que necesitesout.strip().split("\n")yjson.loadsen cada línea. - Olvidar
__init__.py.src/utils/debería ya tener__init__.pydesde la Fase 0. Si no, créalo vacío. - Importaciones circulares.
seeding.pyllama aget_logger;logging.pyno importa seeding. Mantenlo unidireccional.
Cuándo consultar solutions/¶
Después de haber:
- Commiteado ambos archivos de utilidad.
- Ambos archivos de test en verde.
- El smoke experiment ejecutado y puedes pegar la salida JSON en tu
README.md.
Entonces lee solutions/00-environment-and-utilities-ref.md (escrito en la apertura de fase) para comparar las elecciones de estructura.
Siguiente lab: lab/01-strides-and-views.md.