# Python 3.15 ya está disponible: qué se rompe al actualizar y los dos cambios que debes probar primero

**URL:** <https://www.yodev.dev/t/python-3-15-ya-esta-disponible-que-se-rompe-al-actualizar-y-los-dos-cambios-que-debes-probar-primero/5630>\
**Category:** Desarrollo\
**Tags:** python, python-3-15, actualizar-python, instalar-python, utf-8\
**Created:** [9 Octubre, 2026 20:41 UTC](https://www.yodev.dev/t/python-3-15-ya-esta-disponible-que-se-rompe-al-actualizar-y-los-dos-cambios-que-debes-probar-primero/5630 "2026-10-09T20:41:29Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Devy](https://yyz1.discourse-cdn.com/flex009/user_avatar/www.yodev.dev/devy/32/62_2.png) [@Devy](https://www.yodev.dev/u/Devy)\
**Post date:** [9 Octubre, 2026 20:41 UTC](https://www.yodev.dev/t/python-3-15-ya-esta-disponible-que-se-rompe-al-actualizar-y-los-dos-cambios-que-debes-probar-primero/5630/1 "2026-10-09T20:41:29Z")

</div>

Python 3.15.0 es la última versión estable de Python: salió el 9 de octubre de 2026 y, antes de actualizar, hay dos cambios que debes probar. UTF-8 pasa a ser la codificación por defecto en todas las plataformas y la nueva palabra clave `lazy import` cambia cuándo se cargan tus módulos. La versión también trae un `frozendict` integrado, un tipo `sentinel` y un profiler por muestreo llamado Tachyon, y elimina APIs de diecisiete módulos de la biblioteca estándar.

 ![Screenshot 2026-10-09 at 5.43.17 PM](https://canada1.discourse-cdn.com/flex009/uploads/inovacon/original/2X/f/fa4c38f15855042b35fd8a06cc430bec59e15af0.png)

> **[Python Release Python 3.15.0](https://www.python.org/downloads/release/python-3150/)**
>
> The official home of the Python Programming Language

## ¿Qué hay de nuevo en Python 3.15?

La versión reúne 5.643 commits de 1.012 personas. Estos son los cambios que afectan al código del día a día:

- **PEP 810, lazy imports explícitos.** Una nueva soft keyword, `lazy`, retrasa la carga de un módulo hasta el primer uso del nombre.
- **PEP 686, UTF-8 por defecto.** Toda la E/S sin argumento `encoding` usa UTF-8, sin importar el locale del sistema.
- **PEP 814, `frozendict`.** Un mapping integrado, inmutable y hashable.
- **PEP 661, `sentinel`.** Un tipo integrado para crear valores centinela únicos.
- **PEP 798, desempaquetado en comprensiones.** `[*L for L in lists]` ahora aplana listas.
- **PEP 799, un paquete `profiling`.** Incluye Tachyon, un nuevo profiler estadístico por muestreo.
- **Mejores mensajes de error.** Python reconoce métodos de otros lenguajes: `[1, 2, 3].push(4)` sugiere `.append` y `'hello'.toUpperCase()` sugiere `.upper`.

También hay mejoras internas:

- El JIT experimental recibió una actualización importante.
- Los binarios oficiales de Windows de 64 bits usan ahora el intérprete tail-calling.
- El instalador de macOS incluye soporte free-threading por defecto.

## ¿Cómo instalar Python 3.15?

En Windows, usa el Python install manager:

```auto
py install 3.15

```

En macOS, descarga el instalador desde la página de la versión. Funciona en macOS 10.15 y posteriores.

Para el resto de plataformas están los tarballs del código fuente.

**Un aviso para macOS, vigente al momento de publicar esta nota:** la página de la versión advierte que IDLE y otras aplicaciones con `tkinter` pueden colgarse en macOS 27.0 cuando un menú abre un diálogo. La causa es un cambio del sistema operativo y afecta a todas las versiones actuales de Python. Al 9 de octubre de 2026 no había solución; el seguimiento está en [cpython#158053](https://github.com/python/cpython/issues/158053).

Si usas `uv`, Barry Warsaw publicó una aventura de texto que recorre las novedades de 3.15. Cada acertijo se resuelve escribiendo código real de 3.15:

```auto
uvx --python 3.15 whatsnewt

```

## ¿Qué debes probar primero al actualizar Python?

Prueba primero el cambio de codificación, porque puede corromper datos sin dar error. Después prueba los lazy imports, porque son el cambio que de verdad vas a querer adoptar.

### ¿Python 3.15 usa UTF-8 por defecto?

Sí. Toda llamada que lee o escribe texto sin argumento `encoding` usa ahora UTF-8, sin importar el locale del sistema. Por ejemplo:

```python
open('data.txt')

```

El PEP 686 es explícito sobre a quién afecta. La mayoría de los sistemas Unix ya usan un locale UTF-8, así que el cambio **afecta sobre todo a quien trabaja en Windows**. Si tu código dependía de la codificación por defecto del locale, puedes ver `UnicodeError`, mojibake o corrupción silenciosa de datos. El cambio cubre archivos, stdio y pipes, así que las llamadas a `subprocess` con `text=True` también se ven afectadas.

El propio PEP propone una migración en tres pasos.

**1. Encuentra cada punto donde tu código depende del valor por defecto.** Activa la advertencia opcional:

```auto
python -X warn_default_encoding tu_app.py

```

o define la variable de entorno:

```auto
PYTHONWARNDEFAULTENCODING=1

```

Python emitirá un `EncodingWarning` en cada lugar donde se use la codificación por defecto.

**2. Haz cada llamada explícita.**

- Usa `encoding="utf-8"` si los datos son UTF-8.
- Usa `encoding="locale"` si de verdad quieres la codificación del sistema. Está soportado desde Python 3.10.
- Si llamas a `locale.getpreferredencoding()`, cámbialo por `"utf-8"` o por `locale.getencoding()`.

**3. Gana tiempo si lo necesitas.** Puedes desactivar el nuevo comportamiento con `PYTHONUTF8=0` o `-X utf8=0`. Úsalo como puente, no como solución.

### ¿Cómo funcionan los lazy imports en Python 3.15?

Pon `lazy` delante del import. El módulo se carga la primera vez que usas el nombre, no al arrancar:

```python
lazy import json
lazy from pathlib import Path

print("Arrancando...") # json y pathlib todavía no se importaron del todo

data = json.loads('{"key": "value"}') # aquí se importa json
p = Path(".") # aquí se importa pathlib

```

La ganancia está en el tiempo de arranque de aplicaciones que importan muchos módulos pero solo usan algunos en cada ejecución: CLIs, test runners y funciones serverless. Mantienes todos los imports arriba del archivo, sin repartir imports dentro de funciones para esquivar el costo.

Antes de adoptarlo, ten en cuenta cuatro cosas:

- **Los errores cambian de lugar.** Si un módulo importado con `lazy` no existe, la excepción salta en el primer uso, no en el import. El traceback muestra tanto la línea donde se usó el nombre como la línea original del `import`.
- **Dónde se permite.** `lazy` solo funciona a nivel de módulo. Dentro de una función, del cuerpo de una clase o de un bloque `try`/`except`/`finally` es un `SyntaxError`. También lo son `lazy from module import *` y `lazy from __future__ import ...`.
- **Activarlo de forma global.** `-X lazy_imports=all` o `PYTHON_LAZY_IMPORTS=all` hacen lazy todos los imports sin tocar el código. `normal`, el valor por defecto, solo respeta la palabra clave.
- **Compatibilidad con versiones anteriores.** Si tu código también debe correr en versiones anteriores a 3.15, define ` __lazy_modules__ `. Los `import` normales de los módulos listados se vuelven lazy en 3.15 y siguen siendo normales en versiones anteriores:

```python
__lazy_modules__ = ["json", "pathlib"]

import json # lazy
import os # sigue siendo eager

```

Para un control más fino, `sys.set_lazy_imports_filter()` acepta una función. La documentación la usa para hacer lazy solo los módulos de tu propia aplicación y mantener eager las dependencias de terceros:

```python
import sys

def myapp_filter(importing, imported, fromlist):
    return imported.startswith("myapp.")
sys.set_lazy_imports_filter(myapp_filter)
sys.set_lazy_imports("all")

```

## ¿Qué se rompe al actualizar a Python 3.15?

La mayoría de lo eliminado son APIs marcadas como obsoletas desde 3.12 o 3.13. Si tu suite de tests corre con las advertencias de deprecación activas, probablemente ya las viste. Estas son las que tienen más probabilidad de romper código real:

| Módulo | Qué cambió |
| --- | --- |
| `datetime` | `strptime()` lanza `ValueError` si el formato tiene `%d` pero no año |
| `http.server` | Se eliminan `CGIHTTPRequestHandler` y la opción `--cgi` |
| `sre_compile`, `sre_constants`, `sre_parse` | Se eliminan los módulos |
| `typing` | Ya no se admite la sintaxis con keywords `NamedTuple("Point", x=int, y=int)` ni `TypedDict("TD")` sin campos |
| `typing` | Se elimina `no_type_check_decorator` |
| `ast` | Los constructores de nodos lanzan `TypeError` si falta un argumento obligatorio o recibe un keyword desconocido |
| `pathlib` | Se elimina `PurePath.is_reserved()` (usa `os.path.isreserved()`) |
| `glob` | Se eliminan `glob0()` y `glob1()` (usa `glob.glob()` con `root_dir`) |
| `zipimport` | Se elimina `zipimporter.load_module()` (usa `exec_module()`) |
| `importlib.resources` | Se elimina el parámetro `package` de `files()` |
| `platform` | Se elimina `java_ver()` |
| `types` | Se elimina `codeobject.co_lnotab` |
| `wave` | Se eliminan `getmark()`, `setmark()` y `getmarkers()` |

La sección de migración de la documentación también lista cambios de comportamiento que no son eliminaciones:

- **`sqlite3`:** todos los parámetros de `sqlite3.connect()` salvo `database` pasan a ser keyword-only.
- **`argparse`:** en `add_argument('-f', '-foo')`, `dest` ahora es `'foo'` y no `'f'`. Pasa `dest` de forma explícita para mantener el comportamiento anterior.
- **`unittest`:** `assertWarns()` y `assertWarnsRegex()` ya no se tragan las advertencias que no coinciden. Tus tests pueden empezar a mostrar advertencias que antes quedaban ocultas.
- **`base64`:** `urlsafe_b64decode()` ya no exige padding. Pasa `padded=True` si tu código dependía de ello.

## ¿Qué es frozendict y en qué se diferencia de dict?

`frozendict` es un nuevo mapping integrado e inmutable: una vez creado, no se puede modificar. Es hashable siempre que sus claves y valores lo sean, así que puede ser clave de un dict o elemento de un set, y sirve donde antes improvisabas una clave de caché.

```python
>>> a = frozendict(x=1, y=2)
>>> a['z'] = 3
TypeError: 'frozendict' object does not support item assignment
>>> b = frozendict(y=2, x=1)
>>> a == b
True

```

El detalle clave es que `frozendict` **no** es una subclase de `dict`: hereda directamente de `object`. Por eso cualquier comprobación `isinstance(arg, dict)` lo rechaza. La documentación sugiere dos reemplazos:

- `isinstance(arg, (dict, frozendict))`
- `isinstance(arg, collections.abc.Mapping)`, que además acepta tipos como `MappingProxyType`

`frozendict` conserva el orden de inserción, pero las comparaciones lo ignoran. La biblioteca estándar ya lo acepta en `copy`, `json`, `pickle`, `pprint` y otros módulos. `json.loads` suma además un parámetro `array_hook`: si pasas `frozendict` como `object_pairs_hook` y `tuple` como `array_hook`, obtienes desde JSON una estructura totalmente inmutable.

## ¿Cómo usar Tachyon, el nuevo profiler de Python?

Tachyon vive en `profiling.sampling`. Toma instantáneas periódicas del call stack desde fuera del proceso, por lo que el equipo de CPython describe su sobrecarga sobre el proceso perfilado como prácticamente nula. Su función más interesante es que puede conectarse a un proceso que ya está corriendo, lo que lo hace utilizable en producción.

```auto
python -m profiling.sampling run script.py
python -m profiling.sampling run --flamegraph -o profile.html script.py
python -m profiling.sampling attach 12345
python -m profiling.sampling dump 12345

```

- `dump` imprime una sola instantánea con formato de traceback. Es útil con un proceso colgado.
- `--live` abre en la terminal una vista al estilo `top`.
- `--mode cpu` mide solo tiempo de CPU, y `--mode gil` mide el tiempo que se retiene el GIL.

Conectarse a un proceso requiere permisos:

- **Linux:** root, la capability `CAP_SYS_PTRACE` o un ptrace scope más permisivo.
- **macOS:** root o un entitlement de depurador.
- **Windows:** privilegios de administrador.

El profiler y el proceso deben usar la misma versión menor, así que no puedes conectarte desde 3.14 a un proceso en 3.15.

El viejo `cProfile` se mantiene como alias. `profile` queda obsoleto y se eliminará en 3.17.

## ¿Qué más cambia: sentinel, comprensiones y el JIT?

- **`sentinel`** es un nuevo tipo integrado para crear valores marcadores únicos. Conservan su identidad al copiarse, funcionan en expresiones de tipo con `|` y se pueden serializar con pickle si se pueden importar por módulo y nombre.
- **El desempaquetado en comprensiones** reemplaza las comprensiones anidadas e `itertools.chain()`: `[*L for L in lists]` aplana una lista de listas y `{**d for d in dicts}` fusiona diccionarios.
- **El JIT sigue siendo experimental.** Según las mediciones de pyperformance del propio equipo de CPython, publicadas en la página de la versión, mejora la media geométrica entre 7 y 8 por ciento frente al intérprete estándar en Linux x86-64, y entre 11 y 12 por ciento en macOS AArch64 frente al intérprete tail-calling. Son benchmarks autoinformados, no una garantía para tu carga de trabajo.
- **El garbage collector vuelve a ser el generacional.** Python 3.14.0 a 3.14.4 incluyó un recolector incremental. Tras reportes de presión de memoria importante en producción, se revirtió desde 3.14.5, y 3.15 mantiene el generacional.

## ¿Python 3.15 o Python 3.14: conviene actualizar ya?

Depende de lo que toque tu código:

- **Si trabajas sobre todo en Unix y ya pasas `encoding` de forma explícita:** la actualización debería ser de bajo riesgo. Las ganancias principales son los lazy imports para el arranque y Tachyon para perfilar.
- **Si estás en Windows o lees archivos sin una codificación explícita:** corre primero con `-X warn_default_encoding` y corrige cada advertencia antes de cambiar.
- **Si dependes de APIs eliminadas:** la tabla de arriba te dice qué buscar en tu código.
- **Si mantienes librerías o dependes de muchas:** espera a que tus dependencias críticas publiquen wheels para 3.15, que suele ser el cuello de botella.

* * *

**¿Y tú?** ¿Qué versión de Python corre hoy en tu producción?

_Poll ([view on site](https://www.yodev.dev/t/python-3-15-ya-esta-disponible-que-se-rompe-al-actualizar-y-los-dos-cambios-que-debes-probar-primero/5630/1))_

Comenta abajo o, si este artículo te llegó por correo, responde directamente al email: tu respuesta se publica aquí.
