*Python 3.15 es la versión de CPython que añade imports perezosos con la palabra clave lazy, los tipos integrados frozendict y sentinel, UTF-8 como codificación por defecto, desempaquetado con `y` en comprensiones y Tachyon, un perfilador por muestreo. El calendario oficial fija la versión final para el 9 de octubre de 2026, una semana más tarde de lo previsto. Aquí está cada novedad ejecutada en Docker, con una medición del arranque con y sin lazy y las roturas que me encontré al actualizar. Tienes también la versión en inglés de este análisis de Python 3.15.

Puntos clave

  • En una herramienta de línea de comandos (CLI) que importa requests, rich y PyYAML, marcar seis imports como lazy bajó el arranque de --version de 73,5 ms a 9,8 ms y los módulos cargados de 342 a 62.
  • PYTHON_LAZY_IMPORTS=all hace perezosos todos los imports sin tocar el código, pero rompe los módulos que se registran al importarse: mi registro de plugins quedó vacío.
  • frozendict es un diccionario inmutable y hashable que no hereda de dict, así que isinstance(x, dict) devuelve False.
  • Con un locale Latin-1, un open() sin encoding que en 3.14.7 leía Muñoz lanza UnicodeDecodeError en 3.15; PYTHONUTF8=0 recupera el comportamiento anterior.
  • Tachyon (python -m profiling.sampling) perfila un script o un proceso en marcha a 1 000 muestras por segundo por defecto y genera flamegraphs y mapas de calor en HTML.
  • Desaparecen sre_compile, el modo --cgi de http.server y los argumentos posicionales de sqlite3.connect() más allá del primero.

Qué trae Python 3.15 y cuándo sale

Python 3.15 añade ocho cambios de lenguaje e intérprete que se notan en el código del día a día. Los recoge la página What’s New de Python 3.15[1], que edita Hugo van Kemenade, también gestor de esta versión.

El PEP 790, el calendario de Python 3.15[2], cerró la entrada de funciones nuevas con la beta 1, el 7 de mayo de 2026. La final estaba fijada para el jueves 1 de octubre, pero una tercera candidata, publicada el 2 de octubre, la movió al viernes 9 de octubre de 2026. La 3.15 recibe correcciones cada dos meses durante dos años y parches de seguridad hasta octubre de 2031, aproximadamente.

Esta tabla resume las novedades que probé y lo que tienes que vigilar con cada una:

Novedad PEP Qué hace Qué vigilar
lazy import 810 Retrasa la carga del módulo hasta el primer uso Los errores de import salen al usar el nombre, no al arrancar
frozendict 814 Diccionario inmutable y hashable No es subclase de dict
sentinel 661 Valores centinela con nombre y repr legible Solo se serializa con pickle si es importable por módulo y nombre
UTF-8 por defecto 686 open() sin encoding usa UTF-8 Rompe ficheros en la codificación del locale
* y ** en comprensiones 798 [*l for l in listas] aplana listas Es sintaxis nueva: 3.14 da SyntaxError
profiling y Tachyon 799 Perfilador por muestreo sin modificar el código profile queda obsoleto y se elimina en 3.17
Frame pointers 831 Pilas nativas fiables para perf y eBPF Las extensiones con su propio sistema de compilación deben conservar los flags
ABI estable free-threaded 803 Extensiones abi3t válidas para builds sin GIL Las herramientas de compilación aún no generan abi3t

Cómo probé Python 3.15 en Docker

Todas las pruebas corrieron el 27 de septiembre de 2026 con la imagen oficial python:3.15.0rc2-slim, la segunda de las tres candidatas, sobre Debian 13 (trixie). La máquina es un Linux aarch64 con 18 núcleos, sin GPU, dentro de una máquina virtual de OrbStack. La página de la 3.15.0rc2 en python.org[3] lo deja claro: "only reviewed code changes which are clear bug fixes are allowed between this release candidate and the final release". Por eso lo que cuento aquí vale para la 3.15.0.

docker run --rm python:3.15.0rc2-slim python -VV
Python 3.15.0rc2 (main, Sep 19 2026, 01:05:41) [GCC 14.2.0]

Para comparar usé python:3.14.7-slim-trixie. Tras la publicación de la final, las etiquetas python:3.15 y python:3.15-slim del repositorio oficial de Python en Docker Hub[4] apuntan a la 3.15.0; en tus Dockerfile cambia 3.15.0rc2-slim por 3.15-slim. La imagen oficial se compila con --enable-optimizations y --with-lto, pero sin JIT: sys._jit.is_available() devuelve False.

Cómo funcionan los lazy imports

Un lazy import crea un objeto proxy en lugar de cargar el módulo, y la carga real ocurre la primera vez que usas el nombre. El PEP 810 de imports perezosos explícitos[5] lo firman Pablo Galindo Salgado, Germán Méndez Bravo, Thomas Wouters, Dino Viehland, Brittany Reynoso, Noah Kim y Tim Stumbaugh. lazy es una palabra clave blanda (soft keyword): solo tiene ese significado delante de import o from, y lazy = "algo" sigue siendo una asignación válida.

Esta es la cabecera de la CLI de prueba, un pequeño generador de informes que lee YAML y pinta una tabla con rich:

import argparse
lazy import json
lazy import sqlite3
lazy import requests
lazy import yaml
lazy from rich.console import Console
lazy from rich.table import Table

Con --version el programa imprime la versión y sale, así que ninguno de esos seis imports llega a cargar su módulo. El proxy se ve si inspeccionas globals(): antes del primer uso, type(globals()["json"]) devuelve <class 'lazy_import'>, y después, <class 'module'>. El tipo está disponible como types.LazyImportType.

Qué pasa cuando un lazy import falla

Si el módulo no existe, el programa arranca sin error y la excepción aparece en la línea que usa el nombre. El traceback encadena las dos ubicaciones, la del import y la del uso:

arranca sin error
Traceback (most recent call last):
  File "/w/fallo.py", line 1, in <module>
    lazy import modulo_que_no_existe
ImportError: lazy import of 'modulo_que_no_existe' raised an exception
during resolution

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/w/fallo.py", line 6, in <module>
    modulo_que_no_existe.hacer()
ModuleNotFoundError: No module named 'modulo_que_no_existe'

lazy solo se admite en el nivel del módulo. Dentro de una función, Python 3.15 responde SyntaxError: lazy import not allowed inside functions, y tampoco lo acepta en clases, bloques try ni con import *. Si tu código tiene que seguir funcionando en 3.14, declara __lazy_modules__ = ["json"] al principio del módulo. La 3.15 trata entonces import json como perezoso, y la 3.14 ignora la variable y lo carga al momento; lo comprobé con las dos imágenes.

El modo global rompe los imports con efectos secundarios

La variable PYTHON_LAZY_IMPORTS=all (o -X lazy_imports=all) vuelve perezosos todos los imports sin cambiar una línea. El riesgo está en los módulos que hacen algo al importarse. En mi prueba, plugin_csv.py se añade a una lista de plugins cuando se importa, y app.py lo importa sin usarlo:

$ docker run --rm -v "$PWD":/w -w /w python:3.15.0rc2-slim python app.py
plugins: ['csv']
$ docker run --rm -e PYTHON_LAZY_IMPORTS=all -v "$PWD":/w -w /w \
    python:3.15.0rc2-slim python app.py
plugins: []

Con el modo global, plugin_csv nunca se ejecuta porque nadie usa su nombre, y el registro queda vacío sin ningún error. Si quieres el modo global, limita su alcance con sys.set_lazy_imports_filter(), que recibe el módulo que importa y el importado y decide cuáles pueden ser perezosos.

Cuánto arranque ahorran los lazy imports

Marcar como lazy los seis imports redujo el arranque de --version un 87 %, de 73,5 ms a 9,8 ms de mediana. Medí cada caso 50 veces con un script que lanza el intérprete con subprocess y cronometra con time.perf_counter(), dentro del mismo contenedor.

Solo acepté tandas con la carga media del último minuto por debajo de 3 al empezar y al terminar. En las cuatro que publico se movió entre 1,09 y 1,15. Anoto también el tiempo de CPU del proceso hijo, que en estas condiciones coincide con el tiempo real.

Tiempo de ejecución de cada caso, Python 3.15.0rc2 y 3.14.7 ms · menos es mejor
python -c pass (referencia), 3.15.0rc2
Mediana: 5,0 ms
p90: 5,5 ms
CPU: 4,9 ms
--version, imports normales, 3.15.0rc2
Mediana: 73,5 ms
p90: 78,7 ms
CPU: 73,3 ms
--version, seis imports lazy, 3.15.0rc2
Mediana: 9,8 ms
p90: 10,7 ms
CPU: 9,8 ms
--version, PYTHON_LAZY_IMPORTS=all, 3.15.0rc2
Mediana: 9,5 ms
p90: 10,4 ms
CPU: 9,4 ms
Informe de un YAML, imports normales, 3.15.0rc2
Mediana: 71,3 ms
p90: 77,6 ms
CPU: 71,1 ms
Informe de un YAML, imports lazy, 3.15.0rc2
Mediana: 29,2 ms
p90: 30,9 ms
CPU: 29,0 ms
python -c pass (referencia), 3.14.7
Mediana: 4,4 ms
p90: 4,6 ms
CPU: 4,3 ms
--version, imports normales, 3.14.7
Mediana: 66,4 ms
p90: 70,6 ms
CPU: 66,3 ms
Informe de un YAML, imports normales, 3.14.7
Mediana: 68,6 ms
p90: 81,3 ms
CPU: 68,4 ms
Ver tabla de valores
Caso y versiónMedianap90CPU
python -c pass (referencia), 3.15.0rc25,0 ms5,5 ms4,9 ms
--version, imports normales, 3.15.0rc273,5 ms78,7 ms73,3 ms
--version, seis imports lazy, 3.15.0rc29,8 ms10,7 ms9,8 ms
--version, PYTHON_LAZY_IMPORTS=all, 3.15.0rc29,5 ms10,4 ms9,4 ms
Informe de un YAML, imports normales, 3.15.0rc271,3 ms77,6 ms71,1 ms
Informe de un YAML, imports lazy, 3.15.0rc229,2 ms30,9 ms29,0 ms
python -c pass (referencia), 3.14.74,4 ms4,6 ms4,3 ms
--version, imports normales, 3.14.766,4 ms70,6 ms66,3 ms
Informe de un YAML, imports normales, 3.14.768,6 ms81,3 ms68,4 ms

50 ejecuciones por caso en el mismo contenedor, con la carga media entre 1,09 y 1,15.

La diferencia sale de los módulos que no se cargan. -X importtime escribe una línea por módulo importado más una cabecera, así que la versión normal cargó 342 módulos y la perezosa, 62:

python -X importtime informe_eager.py --version 2>imp_eager.txt
grep -c "import time:" imp_eager.txt
343
python -X importtime informe_lazy.py --version 2>imp_lazy.txt
grep -c "import time:" imp_lazy.txt
63

El import más caro era requests, con 37,0 ms acumulados en esa medición. Incluso al generar el informe, cuando yaml y rich sí se cargan, la versión perezosa tardó 29,2 ms frente a 71,3 ms, porque requests y sqlite3 nunca llegan a usarse.

El PEP 810 anticipa este efecto en herramientas de línea de comandos: "This can reduce startup time by 50-70% in practice". Mi 87 % supera esa horquilla porque --version no usa ninguno de los módulos; en un comando real ahorras lo que tu ruta no toca.

Con imports normales, la 3.15.0rc2 arrancó algo más lenta que la 3.14.7: 73,5 ms frente a 66,4 ms. En una segunda tanda con el orden invertido salió 70,4 ms frente a 65,6 ms. Incluso el intérprete vacío (python -c pass) tardó 5,0 ms frente a 4,4 ms.

frozendict: un diccionario inmutable y hashable

frozendict es un tipo integrado que no admite cambios después de crearse y que sirve como clave de diccionario si sus claves y valores son hashables. El PEP 814 de frozendict[6] lo implementaron Victor Stinner y Donghee Na. Este script prueba lo que más te va a importar:

import json, copy
cfg = frozendict(host="db", port=5432)
try:
    cfg["port"] = 6432
except TypeError as e:
    print("TypeError:", e)
otro = frozendict(port=5432, host="db")
print(cfg == otro, hash(cfg) == hash(otro))
print({cfg: "principal"}[otro])
print(isinstance(cfg, dict), json.dumps(cfg))
print(copy.copy(cfg) is cfg)
print(cfg | {"port": 6432})

La salida en 3.15.0rc2 fue esta:

TypeError: 'frozendict' object does not support item assignment
True True
principal
False {"host": "db", "port": 5432}
True
frozendict({'host': 'db', 'port': 6432})

La igualdad ignora el orden de inserción, así que dos frozendict con las mismas claves en distinto orden tienen el mismo hash. El operador | devuelve un frozendict nuevo sin tocar el original. json, copy, pickle y pprint lo aceptan, pero el False de isinstance(cfg, dict) es la dificultad: si tu código valida con isinstance(x, dict), cámbialo por isinstance(x, collections.abc.Mapping).

sentinel: valores centinela con nombre

sentinel crea un objeto único con un repr legible, para distinguir "no me han pasado nada" de None. Es el PEP 661 de valores centinela[7], escrito por Tal Einat e implementado por Jelle Zijlstra. Hasta ahora lo habitual era MISSING = object(), que se imprime como <object object at 0x…>.

import copy, pickle
MISSING = sentinel("MISSING")

def get(d, k, default=MISSING):
    v = d.get(k, default)
    if v is MISSING:
        raise KeyError(k)
    return v

print(MISSING, copy.deepcopy(MISSING) is MISSING)
print(pickle.loads(pickle.dumps(MISSING)) is MISSING)
print(int | MISSING)
print(get({"a": None}, "a"))

El script imprimió MISSING True, True, int | MISSING y None. La copia profunda y pickle conservan la identidad, así que is MISSING sigue funcionando tras serializar, y int | MISSING vale como anotación de tipo.

Desempaquetado con * y ** en comprensiones

Las comprensiones de listas, conjuntos y diccionarios aceptan ahora * y **, lo que sustituye a los bucles anidados y a itertools.chain(). El PEP 798 de desempaquetado en comprensiones[8] extiende la sintaxis del PEP 448 a este caso:

listas = [[1, 2], [3, 4], [5]]
print([*l for l in listas])
print({*s for s in [{1, 2}, {2, 3}]})
defaults = {"retries": 3, "timeout": 10}
env = {"timeout": 30}
print({**d for d in (defaults, env)})

La 3.15.0rc2 imprimió [1, 2, 3, 4, 5], {1, 2, 3} y {'retries': 3, 'timeout': 30}: en diccionarios gana el último valor. El mismo fichero en 3.14.7 falla antes de ejecutarse con SyntaxError: iterable unpacking cannot be used in comprehension, así que no lo uses en librerías que aún soporten versiones anteriores.

Cómo perfilar con Tachyon sin tocar el código

Tachyon es el perfilador por muestreo del nuevo paquete profiling: en lugar de instrumentar cada llamada, lee la pila del proceso a intervalos. La documentación de profiling.sampling[9] fija la frecuencia por defecto en 1 kHz y admite modos por tiempo real, CPU, GIL o excepciones. cProfile sigue existiendo como alias de profiling.tracing, y el módulo profile queda obsoleto hasta su eliminación en 3.17.

Lo probé con un script que serializa 20 000 filas a JSON y calcula su hash SHA-256, 40 veces:

python -m profiling.sampling run --pstats lento.py
python -m profiling.sampling run --flamegraph -o flame.html lento.py
python -m profiling.sampling run --heatmap -o heat lento.py

La primera orden capturó 1 109 muestras en 1,11 s, a 997,64 muestras por segundo. Esta es la salida recortada a tres columnas:

nsamples  sample%  filename:lineno(function)
 382/382     40.9  encoder.py:263(JSONEncoder.iterencode)
 166/166     17.8  lento.py:7(resumir)
 144/757     15.4  lento.py:4(serializar)

El 40,9 % de las muestras cayó dentro del codificador JSON y el 17,8 % en el cálculo del hash. El modo --heatmap lleva ese dato a cada línea del código fuente (la captura es de otra ejecución del mismo script):

Mapa de calor por línea de Tachyon en Python 3.15: la línea que llama a json.dumps acumula 1288 muestras y la del hash SHA-256, 223, sobre 1538 muestras en total.

Para un proceso que ya está en marcha, python -m profiling.sampling attach -d 2 --pstats PID muestrea durante dos segundos y dump PID imprime la pila actual, útil con procesos colgados. En mi contenedor los dos funcionaron incluso como usuario sin privilegios, pero porque este host tiene ptrace_scope a 0. La documentación avisa de que en Linux necesitas ser root, tener CAP_SYS_PTRACE o relajar ese ajuste, así que en Docker añade --cap-add SYS_PTRACE si falla. El perfilador y el proceso deben usar la misma versión menor de Python.

Qué rompe UTF-8 como codificación por defecto

Python 3.15 usa UTF-8 en open() y en el resto de E/S de texto cuando no pasas encoding, sin mirar el locale del sistema. Es el PEP 686, que hace UTF-8 el modo por defecto[10], escrito por Inada Naoki. En la imagen oficial de Docker no notarás nada: no define locale, y con el locale C la 3.14.7 ya activaba el modo UTF-8 (sys.flags.utf8_mode valía 1). El cambio muerde en sistemas con locales heredados.

Para reproducirlo, generé el locale es_ES ISO-8859-1 en las dos imágenes y leí un CSV exportado en Latin-1 con open("clientes.csv"), sin encoding:

3.14.7 utf8_mode: 0 locale: ISO-8859-1
'id;nombre\n1;Mu\xf1oz\n'

3.15.0rc2 utf8_mode: 1 locale: ISO-8859-1
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 14:
invalid continuation byte

El arreglo correcto es declarar la codificación: con open("clientes.csv", encoding="latin-1") la 3.15 leyó Muñoz otra vez. Como parche temporal, PYTHONUTF8=0 devolvió el comportamiento de 3.14. Para encontrar estas llamadas antes de migrar, ejecuta tus pruebas en 3.14 con -X warn_default_encoding: en mi caso marcó la línea exacta con un EncodingWarning.

Frame pointers y JIT: lo que cambia por debajo

CPython 3.15 se compila por defecto con -fno-omit-frame-pointer y -mno-omit-leaf-frame-pointer, según el PEP 831 de frame pointers[11]. En la imagen oficial comprobé que los dos flags están en sysconfig.get_config_var("CFLAGS"). Con ellos, herramientas como perf o los perfiladores basados en eBPF reconstruyen la pila nativa sin tablas de desenrollado, un problema que ya tratamos en profiling continuo con eBPF en producción.

El JIT experimental también mejora. La página What’s New cita un 7-8 % de mejora media geométrica en pyperformance sobre el intérprete estándar en Linux x86-64, y un 11-12 % en macOS AArch64. No lo he medido: la imagen oficial de Docker no lo incluye y habría que compilar CPython con LLVM 21. El GIL sigue activo en la build estándar; la opción sin GIL es la que explicamos en Python 3.13 con GIL opcional, y la 3.15 le añade el ABI estable abi3t para extensiones.

Qué se elimina y qué te encuentras al actualizar

La 3.15 retira APIs que 3.13 y 3.14 ya marcaban como obsoletas. Ejecuté el mismo script en las dos versiones; en 3.14.7 cada caso era un DeprecationWarning y en 3.15.0rc2 fue esto:

sre_compile: ModuleNotFoundError: No module named 'sre_compile'
sqlite3 posicional: TypeError: connect() takes at most 1 positional
arguments (2 given)
strptime %d sin año: ValueError: Day of month directive '%d' may not be
used without a year directive.
import profile: DeprecationWarning The profile module is deprecated and
will be removed in Python 3.17.

python -m http.server --cgi también falla ahora con unrecognized arguments: --cgi, porque se eliminó CGIHTTPRequestHandler. Otras eliminaciones de la lista oficial son typing.no_type_check_decorator, codeobject.co_lnotab y el método load_module() de los cargadores de importlib. Además, re.prefixmatch() llega como nombre explícito de re.match(), que no se retira.

Los mensajes de error siguen mejorando, en la línea de lo que contamos en Python 3.12 y sus errores que por fin ayudan. [1, 2].push(3) responde ahora Did you mean '.append'? y 'hola'.toUpperCase(), Did you mean '.upper'?; en 3.14.7 no había sugerencia.

Ruedas binarias para cp315

El 27 de septiembre de 2026 comprobé con pip download --only-binary=:all: qué paquetes compilados tenían ruedas cp315 para aarch64. Las encontré para estos:

  • numpy 2.5.3 y pandas 3.0.6
  • pydantic-core 2.49.0 y orjson 3.12.0
  • lxml 6.1.3, pillow 12.3.0 y psycopg-binary 3.3.6
  • cryptography 50.0.1, con una rueda abi3 que sirve para cualquier CPython desde la 3.11

Según python.org, las ruedas compiladas contra las candidatas "will work with future versions of Python 3.15", así que no necesitan recompilarse para la final.

Cómo actualizar una imagen Docker a Python 3.15

La actualización se reduce a cambiar la etiqueta base y ejecutar las pruebas con las advertencias convertidas en errores. Este es el Dockerfile que usé para las pruebas con dependencias:

FROM python:3.15.0rc2-slim
RUN pip install --no-cache-dir --root-user-action=ignore \
    requests rich pyyaml

Antes de cambiar la etiqueta a python:3.15-slim en producción, sigue estos pasos en orden:

  1. Ejecuta las pruebas en 3.14 con -W error::DeprecationWarning -X warn_default_encoding para ver lo que la 3.15 eliminará y los open() sin codificación.
  2. Añade encoding= explícito a cada open() que lea ficheros que no genera tu aplicación.
  3. Comprueba con pip download --only-binary=:all: que tus dependencias compiladas tienen ruedas cp315.
  4. Cambia la etiqueta base y repite las pruebas; si usas isinstance(x, dict) para validar configuración, revisa si te llegarán frozendict.
  5. Mide el arranque con python -X importtime y marca como lazy los imports que tu ruta más frecuente no usa.

Preguntas frecuentes

¿Cuándo sale Python 3.15 y hasta cuándo tiene soporte?

El PEP 790 fija la 3.15.0 final para el 9 de octubre de 2026; la fecha inicial era el 1 de octubre. Recibe correcciones cada dos meses durante dos años y parches de seguridad hasta octubre de 2031, aproximadamente.

¿Tengo que reescribir mis imports para usar los lazy imports?

No. Puedes marcar solo los imports caros con lazy, usar __lazy_modules__ si mantienes compatibilidad con 3.14, o activar el modo global con PYTHON_LAZY_IMPORTS=all. El modo global omite los módulos que se registran al importarse, así que pruébalo antes en tu código.

¿Python 3.15 es más rápido que 3.14 sin cambiar nada?

No en mi prueba. Con imports normales, la 3.15.0rc2 arrancó mi CLI algo más lenta que la 3.14.7: 73,5 ms frente a 66,4 ms de mediana. La ganancia grande llega al marcar imports como lazy. El JIT promete un 7-8 % en x86-64, pero la imagen oficial de Docker no lo trae.

Conclusión

Python 3.15 se nota sobre todo en el arranque y en la ergonomía. lazy convierte en una palabra lo que antes eran imports escondidos dentro de funciones, y frozendict y sentinel sustituyen patrones caseros. Las roturas reales son pocas y predecibles: ficheros leídos sin encoding, módulos eliminados tras dos versiones de aviso y parámetros de sqlite3 que pasan a ser solo por nombre. Empieza por ejecutar tus pruebas en 3.14 con -X warn_default_encoding y después cambia la etiqueta de tu imagen a python:3.15-slim.

Fuentes: [1] What’s New de Python 3.15[1], [2] PEP 790: calendario de Python 3.15[2], [3] PEP 810: imports perezosos explícitos[5], [4] PEP 814: el tipo integrado frozendict[6], [5] PEP 661: valores centinela[7], [6] Documentación de profiling.sampling (Tachyon)[9], [7] PEP 686: UTF-8 como modo por defecto[10], [8] PEP 798: desempaquetado en comprensiones[8], [9] PEP 831: frame pointers por defecto[11], [10] Página de Python 3.15.0rc2 en python.org[3], [11] Imagen oficial de Python en Docker Hub[4], [12] Página de Python 3.15.0rc3 en python.org[12].

Fuentes

  1. página What’s New de Python 3.15
  2. PEP 790, el calendario de Python 3.15
  3. página de la 3.15.0rc2 en python.org
  4. repositorio oficial de Python en Docker Hub
  5. PEP 810 de imports perezosos explícitos
  6. PEP 814 de frozendict
  7. PEP 661 de valores centinela
  8. PEP 798 de desempaquetado en comprensiones
  9. documentación de profiling.sampling
  10. PEP 686, que hace UTF-8 el modo por defecto
  11. PEP 831 de frame pointers
  12. Página de Python 3.15.0rc3 en python.org