Skip to content

Latest commit

 

History

History
135 lines (93 loc) · 10.8 KB

File metadata and controls

135 lines (93 loc) · 10.8 KB

🐍 Python

Versión fijada: 3.12 · Imagen base: python:3.12-alpine · Hub: :8200 · Casos operativos: 12 / 12

⬅️ Volver a los perfiles de lenguaje · 🗺️ Mapa de stacks · 🔄 Protocolo de actualización


🪪 Identidad

Python es un lenguaje interpretado, de tipado dinámico y fuerte, diseñado alrededor de la legibilidad. Su biblioteca estándar es de las más amplias del set —"pilas incluidas" es un lema, no una metáfora— y su ecosistema científico no tiene competencia real.

Para qué se usa en la industria: análisis de datos, machine learning, automatización y scripting, backend web (Django, FastAPI), DevOps y herramientas internas. Es el lenguaje que más frecuentemente se elige por velocidad de la persona que escribe, no por velocidad del programa — y en la mayoría de los contextos esa es la decisión correcta.

Por qué está en este laboratorio: por el GIL. Python es el único stack del set donde el paralelismo de CPU está limitado por diseño, y eso convierte al caso 11 en una demostración honesta de un problema real que ningún otro runtime del laboratorio tiene. También porque su logging de stdlib es, según el veredicto del caso 03, la API más difícil de violar por accidente de los siete stacks.


⚙️ Modelo de ejecución

Threads reales del sistema operativo, serializados por el GIL para la ejecución de bytecode.

El Global Interpreter Lock permite que solo un thread ejecute bytecode de Python a la vez. No impide la concurrencia de I/O —el GIL se libera durante las esperas— pero sí impide el paralelismo de CPU.

Consecuencia Dónde se nota
Concurrencia de I/O sí, paralelismo de CPU no Dos threads esperando a la base avanzan en paralelo; dos threads calculando, no — caso 11
El worker en thread compite con los lectores En el caso 01 el worker que refresca la tabla resumen serializa trabajo de CPU con los handlers — caso 01
La biblioteca estándar cubre casi todo sqlite3, logging, threading, gc, tracemalloc, re — ninguno de los doce casos necesita un paquete de PyPI
Sin tipos en runtime Las anotaciones no se verifican al ejecutar. El error de firma llega con el request — caso 07

🧰 Primitivas que usa el laboratorio

Caso Primitiva central Por qué esta y no otra
01 · API lenta sqlite3 stdlib + threading.RLock + worker en thread Sin dependencias; el GIL hace visible la contención entre worker y lectores
02 · N+1 sqlite3 stdlib Directo y legible: el N+1 queda a la vista en el código, sin ORM que lo disimule
03 · Observabilidad logging.LoggerAdapter + JsonFormatter La API más difícil de violar por accidente del set: el adapter inyecta el contexto en cada registro
04 · Timeouts timeouts de socket / signal Wall-clock. Limitación: abandona el resultado sin liberar el trabajo
05 · Memoria gc + sys.getsizeof + tracemalloc Instrumentación de memoria en la stdlib, sin agente externo
06 · Pipeline dict protegido por lock Correcto; sin red de seguridad de tipos debajo
07 · Monolito dict[str, Callable] Registrar un módulo es una línea. El error de firma aparece en runtime
08 · Extracción callbacks en lista Simple y sin desacople: los subscribers corren en línea
09 · Integración externa threading.Semaphore + re.match para SKUs + set de idempotencia Semáforo de stdlib, correcto y directo
10 · Sobre-arquitectura dict O(1) El "right-sized" del caso
11 · Reportes ThreadPoolExecutor separado Aísla el reporting; el GIL limita el paralelismo real del trabajo CPU
12 · Punto único if x is None / dict.get() Disciplina pura, cero respaldo del lenguaje

💡 El patrón que solo se ve mirando la columna entera: ninguno de los doce casos necesita instalar nada. sqlite3, logging, threading, gc y re alcanzan. Es el argumento más fuerte de Python en este laboratorio y no tiene que ver con rendimiento: tiene que ver con cuánto código de terceros hay que auditar para llegar a producción.


📈 Rendimiento: qué mide el laboratorio y cómo reproducirlo

⚠️ Este repositorio no publica benchmarks entre lenguajes. Se mide la pendiente dentro de cada stack: legacy contra optimized, mismo runtime, misma máquina. Comparar el tiempo absoluto de Python contra Go mediría el intérprete, no el criterio.

Señal De dónde sale Qué caso la expone
avg_ms · p95_ms · p99_ms muestras en memoria 01, 02, 10
objetos vivos y tamaño gc.get_objects() + sys.getsizeof 05
picos de asignación tracemalloc 05
threads del pool en uso ThreadPoolExecutor 11
db_hits por request contador alrededor de sqlite3 01, 02

Reproducir la medición del caso 11 (el GIL como techo):

docker compose -f compose.python.yml up -d --build
curl -s localhost:8200/11/activity
for i in $(seq 1 8); do curl -s "localhost:8200/11/report-legacy?rows=200000" & done; wait
curl -s "localhost:8200/11/order-write"                  # degraded: true — el CPU-bound serializa
curl -s "localhost:8200/11/report-isolated?rows=200000"  # pool separado: aisla, pero el GIL sigue ahi
curl -s localhost:8200/11/diagnostics/summary

Especificación de rendimiento que este stack verifica y ningún otro puede: separar el pool de reporting mejora el aislamiento pero no multiplica el throughput de CPU. En Java o .NET el mismo cambio sí lo multiplica. La comparación entre esos dos resultados es el argumento completo del caso 11, y solo existe porque Python está en el laboratorio.


🚧 Límites, problemas sin solución y desafíos

Límite Por qué importa Dónde se ve
El GIL impide el paralelismo de CPU Separar pools aísla, pero no acelera el trabajo CPU-bound. Es el techo estructural del runtime caso 11
Timeouts sin cancelación real El wall-clock abandona el resultado; el trabajo del otro lado sigue corriendo caso 04
Ausencia sin respaldo del lenguaje is None y dict.get() son disciplina. Nada obliga a manejar el caso vacío caso 12
Sin verificación de tipos en runtime Las anotaciones documentan; no ejecutan. El error de contrato llega con el request caso 07
Callbacks síncronos sin desacople La lista de callbacks del caso 08 notifica en línea: no hay buffer ni consumidor independiente caso 08
Costo del intérprete Para trabajo CPU-bound puro, el orden de magnitud contra un compilado es real y no se cierra optimizando el código transversal

Desafío abierto del stack en este laboratorio: el caso 11 está construido sobre el supuesto de que el GIL serializa el trabajo de CPU. Python 3.13 introduce free-threading (PEP 703) como build opcional sin GIL. Si el laboratorio adoptara ese build, el argumento del caso 11 se invierte por completo — dejaría de ser "el techo del runtime" para pasar a ser "una decisión de configuración". Está anotado como disparador explícito en scripts/language_drift.py.


🏆 Dónde gana y dónde pierde en el laboratorio

Agregado de los veredictos de las 11 comparativas que rankean: 0 primeros puestos, media 5.0.

  • 🥉 Tercero en 03LoggerAdapter + JsonFormatter: la API más difícil de violar por accidente del set completo. Es el mejor resultado de Python en el laboratorio y no tiene nada que ver con rendimiento.
  • 4º en 02, 06 y 09sqlite3 y threading.Semaphore de stdlib, correctos y directos.
  • 6º en 04, 05, 07, 08 y 12 — los casos donde el lenguaje no respalda la corrección con nada.

Lectura honesta: Python queda sexto en cinco casos, y el laboratorio no lo maquilla. Lo que aporta es distinto: es el único stack que expone un límite estructural real —el GIL— y el que llega más lejos sin instalar nada. En un repositorio que compara criterio y no velocidad, ambas cosas cuentan.


🔄 Ciclo de versiones

Versión fijada hoy 3.12 (python:3.12-alpine)
Cadencia upstream Una release menor por año, en octubre
Política de soporte 5 años por versión: 3.12 recibe parches de seguridad hasta octubre de 2028
Producto en endoflife.date python

Qué revisar en el próximo salto:

  1. 🚨 Free-threading (3.13+, PEP 703) — si el laboratorio pasa a un build sin GIL, el argumento del caso 11 deja de ser cierto. Es el disparador de revisión más importante de este stack: no es un bump, es reescribir la narrativa del caso.
  2. Cambios en sqlite3 — es la base de los casos 01 y 02.
  3. Cambios en logging — el caso 03 depende de LoggerAdapter y de un formatter propio.
  4. Subinterpretes (PEP 684) — abren un modelo de aislamiento nuevo que el caso 11 podría contrastar contra el ThreadPoolExecutor actual.

El detalle del procedimiento está en docs/language-upgrade-protocol.md.


🚀 Levantar el stack

docker compose -f compose.python.yml up -d --build

Los 12 casos quedan servidos en http://localhost:8200/NN/. Cada caso trae además su propio compose.yml para correrlo aislado.