Python 3.15: novedades que cambian tu código y tus scripts
Vienes de TypeScript y escribes .toUpperCase() en Python sin querer. Python te devuelve un AttributeError seco que no ayuda nada.
En Python 3.15 ese error termina con una sugerencia: Did you mean '.upper'?. Dice mucho de quién va a notar esta versión: quien usa Python como segundo lenguaje, para scripts y agentes.
Pero la misma release trae otro cambio que no lanza ningún error. Solo hace que tu script lea un CSV distinto al de ayer.
En corto: Python 3.15.0 salió el 9 de octubre de 2026. Trae lazy import para arrancar más rápido, frozendict y sentinel como built-ins, unpacking con * en comprehensions y un profiler de muestreo llamado Tachyon. El cambio que más puede romperte es silencioso: UTF-8 pasa a ser la codificación por defecto, y eso afecta sobre todo a scripts en Windows.
¿Qué es Python 3.15 y cuándo salió?
Python 3.15 es la versión estable de CPython publicada el 9 de octubre de 2026, con unos 5.643 commits de 1.012 contribuidores respecto a la 3.14.
Las cifras vienen del anuncio oficial de Hugo van Kemenade, release manager de la 3.15. Estaba prevista para el 1 de octubre, pero un rc3 sorpresa la retrasó al 9 por un motivo que cuento más abajo. El calendario final está en la PEP 790. La lista completa está en el What's New in Python 3.15.
Novedades de Python 3.15 que afectan a tu código
Las novedades con impacto real en scripts, APIs y agentes, todas confirmadas en la documentación oficial:
| Novedad | Qué hace | A quién afecta | Riesgo o límite |
|---|---|---|---|
lazy import (PEP 810) |
Retrasa la carga de un módulo hasta su primer uso | CLIs, agentes y apps con arranque lento | Los ImportError aparecen al usar el módulo, no al arrancar |
| UTF-8 por defecto (PEP 686) | open(), stdio y pipes usan UTF-8 sin importar el locale |
Scripts en Windows que leen o escriben texto | Archivos en cp1252 pueden leerse mal sin error visible |
frozendict (PEP 814) |
Diccionario inmutable y hasheable | Configuración, cachés, claves compuestas | No es subclase de dict: isinstance(x, dict) da False |
sentinel() (PEP 661) |
Crea valores centinela con buen repr y pickling |
Autores de librerías y APIs con defaults opcionales | Sin equivalente nativo en versiones anteriores |
| Unpacking en comprehensions (PEP 798) | [*L for L in lists] aplana listas |
Cualquiera que transforme datos | Sintaxis nueva: rompe en 3.14 o anteriores |
profiling.sampling / Tachyon (PEP 799) |
Profiler de muestreo que se engancha a procesos vivos | Quien depura rendimiento en producción | Requiere permisos elevados para hacer attach |
| JIT experimental mejorado | 7-8% de media en x86-64 Linux, 11-12% en AArch64 macOS | Código Python puro intensivo en CPU | Sigue siendo experimental; rango de -15% a +100% según el benchmark |
| Mensajes de error | Sugerencias tipo Did you mean '.append'? |
Developers que vienen de JS/TS | Ninguno relevante |
Lazy imports en Python 3.15: arrancar sin cargar lo que no usas
Un lazy import es un import que enlaza el nombre al momento, pero solo carga el módulo la primera vez que lo usas. Hasta ahora Python cargaba todo al arrancar, aunque tu CLI solo fuera a ejecutar --help.
La sintaxis oficial es esta:
lazy import json
lazy from pathlib import Path
print("Starting up...") # json y pathlib aún no se han cargado
data = json.loads(39;{"key": "value"}39;) # json se carga aquí
p = Path(".") # pathlib se carga aquí
Piensa en un agente que importa pandas, un SDK de LLM y un cliente de base de datos, pero casi siempre usa solo uno. Con lazy, pagas cada import cuando lo necesitas.
Hay reglas que la PEP 810 deja claras. Solo funciona a nivel de módulo: dentro de funciones, clases o bloques try es SyntaxError. lazy from module import * también lo es.
Si mantienes una librería que debe seguir funcionando en 3.14, la PEP ofrece __lazy_modules__: un set de nombres de módulo que la 3.15 trata como lazy y las versiones anteriores ignoran. Es el puente de migración.
UTF-8 por defecto en Python 3.15: el cambio que no hace ruido
Desde Python 3.15, si no pasas encoding, Python usa UTF-8 para archivos, stdio y pipes, sin importar el locale del sistema. La PEP 686 lo resume: en la mayoría de sistemas Unix ya era así, de modo que el cambio afecta sobre todo a Windows.
La novedad que más te va a costar no es la más vistosa. Es esta.
Un script de automatización que lee un CSV exportado desde Excel en Windows probablemente funcionaba porque el default era cp1252 y el archivo también. En 3.15 lo leerá como UTF-8. Si tienes suerte, UnicodeDecodeError. Si no, caracteres corruptos que nadie ve hasta que llegan a tu base de datos. La propia PEP habla de posible corrupción silenciosa de datos.
La solución es explícita. Antes de actualizar, activa el aviso de codificación en 3.14 para encontrar cada llamada sin encoding:
python -X warn_default_encoding tu_script.py
Después, añade encoding="utf-8" donde el archivo sea UTF-8 y encoding="locale" donde dependas de la codificación del sistema. Si necesitas tiempo, PYTHONUTF8=0 o -X utf8=0 restauran el comportamiento anterior.
Este es el tipo de bug que repasé en los errores de Python en automatización: el código no falla, falla el dato.
frozendict, unpacking y mejores errores en Python 3.15
frozendict es el diccionario inmutable built-in de Python 3.15: no admite asignaciones y es hasheable si sus claves y valores lo son. Sirve como clave de caché o para configuración que nadie debe mutar:
>>> a = frozendict(x=1, y=2)
>>> a[39;z39;] = 3
TypeError: 39;frozendict39; object does not support item assignment
>>> hash(a) == hash(frozendict(y=2, x=1))
True
El unpacking en comprehensions elimina el doble for que siempre había que releer dos veces:
>>> lists = [[1, 2], [3, 4], [5]]
>>> [*L for L in lists]
[1, 2, 3, 4, 5]
>>> dicts = [{39;a39;: 1}, {39;b39;: 2}, {39;a39;: 3}]
>>> {**d for d in dicts}
{39;a39;: 3, 39;b39;: 2}
Si preparas contexto para un LLM fusionando metadatos de varios documentos, {**d for d in dicts} es justo el patrón que usarías. Lo aplico a ese caso en cómo preparar datos para agentes de IA con Python.
Y los errores ya entienden a quien viene de 'list' object has no attribute 'push'. Did you mean '.append'?. El resto de diferencias están en Python para developers JavaScript.
Tachyon: profiling de un proceso en marcha
Tachyon es el profiler de muestreo de Python 3.15: lee el stack de un proceso en marcha desde fuera, sin instrumentar el código ni reiniciarlo.
El nuevo paquete profiling agrupa el profiler determinista de siempre (profiling.tracing, el antiguo cProfile) y uno nuevo de muestreo, Tachyon, en profiling.sampling. Según el What's New, puede muestrear hasta 1.000.000 Hz (por defecto usa 1 kHz).
Lo interesante para agentes y workers de larga duración es que se engancha a un proceso vivo:
python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345
Treinta segundos de muestreo y un flamegraph en HTML, sin reiniciar nada. La documentación de profiling.sampling avisa: necesita permisos elevados (root o CAP_SYS_PTRACE en Linux, root en macOS, administrador en Windows, entre otras opciones) y la misma versión menor en profiler y proceso.
Cómo probar Python 3.15 hoy con uv
Si usas uv, el post de Astral sobre la 3.15 recomienda actualizar uv primero:
uv self update
uv python upgrade 3.15
uv run --python 3.15 python -c "import sys; print(sys.version)"
Las builds de python-build-standalone con 3.15.0 están en la release 20261009. Crea un entorno aparte, corre tus tests y no toques el Python del sistema.
Cuándo NO actualizar a Python 3.15 todavía
Pruébala ya. No la pongas en producción esta semana. Tres motivos.
Tus dependencias pueden no tener wheels. En el anuncio del rc3, Hugo van Kemenade contaba que el 22,2% de los 360 paquetes más populares ya declaraba soporte para 3.15. La mayoría aún no. Si una dependencia con extensiones en C no publica wheel, pip intentará compilarla. Revisa el árbol antes, como explico en auditar dependencias de npm y pip.
Los lazy imports acaban de estabilizarse. El rc3 existió precisamente por "last-minute lazy-import release blockers", según el mismo hilo. Espera al menos a la 3.15.1 antes de llenar tu código de lazy.
Hay eliminaciones que rompen. Se van sre_compile, sre_parse, Loader.load_module(), CGIHTTPRequestHandler y PurePath.is_reserved(), entre otras. Y la política de event loops de asyncio, incluida WindowsSelectorEventLoopPolicy, está deprecada para eliminarse en 3.16. Si tu script en Windows la usa para evitar problemas con el event loop, la alternativa oficial es asyncio.run(main(), loop_factory=asyncio.SelectorEventLoop).
Qué opina la comunidad de Python 3.15
En el hilo de Hacker News de la release, con más de 300 puntos, varios comentarios celebran por fin los lazy imports, frozendict y el unpacking en comprehensions. Simon Willison apunta otro efecto: cada release nueva marca el fin de soporte de una vieja, en este caso la 3.10, y eso permite a los mantenedores de librerías usar por fin features de la 3.11.
Nathan Goldbaum destaca el nuevo ABI estable abi3t para la build free-threaded, que facilita que las librerías con C soporten Python sin GIL.
Qué hacer hoy con Python 3.15
Una sola cosa: ejecuta tu suite o tu script más importante con -X warn_default_encoding en tu versión actual. Cada aviso es un sitio donde la 3.15 puede cambiarte un dato sin decírtelo. Arréglalo con encoding explícito y la migración será aburrida, que es como debe ser.
Si usas Python para construir agentes, el ebook gratuito El Developer Agéntico cubre la arquitectura completa: tools, memoria, RAG y MCP. Y si quieres llevar ese agente de la idea a producto con Claude Code, es lo que hacemos en Construye con IA.
Preguntas frecuentes
¿Cuándo salió Python 3.15?
Python 3.15.0 se publicó el 9 de octubre de 2026. Estaba prevista para el 1 de octubre, pero el equipo de release añadió un tercer release candidate para resolver bloqueantes de última hora en los lazy imports.
¿Los lazy imports de Python 3.15 son automáticos?
No. Por defecto solo son lazy los imports que llevan la palabra lazy. Existe un modo global con -X lazy_imports=all o la variable PYTHON_LAZY_IMPORTS, pero la PEP 810 lo presenta como una opción avanzada, no pensada para librerías.
¿Python 3.15 rompe mis scripts en Windows?
Puede hacerlo si lees o escribes archivos de texto sin indicar encoding y esos archivos no son UTF-8. Python 3.15 usa UTF-8 por defecto. Añade encoding explícito o desactiva el modo con PYTHONUTF8=0 mientras migras.
¿El JIT de Python 3.15 está listo para producción?
Sigue siendo experimental. La documentación oficial reporta una mejora media del 7-8% en x86-64 Linux y del 11-12% en AArch64 macOS, pero con un rango que va desde un 15% más lento hasta más del doble de rápido según el benchmark. Mide tu caso antes de confiar en él.
¿frozendict sustituye a dict?
No. Es un tipo distinto, inmutable y hasheable, y no hereda de dict. Úsalo para configuración y claves de caché. Ojo con el código que comprueba isinstance(x, dict), porque devolverá False.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
