Latest Pre-release in branch 3.15
3.15.0b2
Released 02 Jun 2026
(8 days ago)
SoftwarePython
Version3.15
Status
Beta
Latest release3.15.0b2
02 Jun 2026
(8 days ago)
Source codehttps://github.com/python/cpython/tree/v3.15.0b2
Python 3.15 ReleasesView full list
Note: Python 3.15 has not been officially released yet. The features and changes described in this article are based on the current development branch and may still change before the final stable release (expected in October 2026).

What Is New in Python 3.15

Python 3.15 makes UTF-8 the default I/O encoding on all platforms (ending the long-standing platform-dependent behavior), ships a built-in high-frequency sampling profiler, introduces the PyBytesWriter C API for efficient buffer building, and improves AttributeError suggestions across nested members. Several long-deprecated items are removed.

Category Change PEP / Reference
Encoding UTF-8 is now the default encoding for all I/O when no encoding is specified PEP 686
New Modules profiling.sampling -- high-frequency sampling profiler, zero source changes PEP 799
C API PyBytesWriter API for building bytes objects without reallocations PEP 782
Error Messages AttributeError suggests attributes of nested members when a name is not found at top level --
Standard Library Improvements to calendar, collections, hashlib, sqlite3 --
Removed Old NamedTuple keyword-argument syntax; zipimport legacy methods; collections.abc.ByteString from __all__ --
C API Cleanup: newer functions replace older PyUnicode and PyImport APIs --

What Does Defaulting to UTF-8 Mean in Practice?

Before Python 3.15, open("file.txt") used the platform's locale encoding -- UTF-8 on most Linux systems, but often Latin-1, CP1252, or another encoding on Windows or older macOS. This caused files written on one system to be read incorrectly on another depending purely on locale settings. PEP 686 makes UTF-8 the default everywhere when no encoding= argument is given.

# Before 3.15: encoding depends on platform locale
with open("data.txt") as f:  # could be UTF-8, Latin-1, CP1252...
    content = f.read()

# Python 3.15+: always UTF-8
with open("data.txt") as f:  # guaranteed UTF-8
    content = f.read()

# If you truly need the platform encoding:
import locale
with open("data.txt", encoding=locale.getpreferredencoding(False)) as f:
    content = f.read()

This breaks code that wrote Latin-1 or CP1252 encoded files and reads them back without specifying encoding=. The fix is explicit: always pass encoding="latin-1" (or whatever the file actually is). Code that already specifies encoding= is unaffected.

How Does the Sampling Profiler Work?

The new profiling.sampling module is a statistical profiler that interrupts execution at a configurable frequency (up to ~1,000,000 Hz) to record the current call stack. Unlike cProfile (which instruments every function call), sampling adds minimal overhead -- typically under 1% -- and can attach to a running process without restarting it.

import profiling.sampling as sp

profiler = sp.Sampler(interval=0.001)  # 1ms = 1000 Hz
profiler.start()
run_workload()
profiler.stop()

stats = profiler.get_stats()
stats.print_top(20)  # top 20 hotspots by sample count

Sampling profilers are the right tool for production profiling where cProfile's call-count instrumentation overhead is unacceptable. The profiler works with both GIL and free-threaded builds, and with the JIT enabled.

What Is the PyBytesWriter C API?

The PyBytesWriter API (PEP 782) provides C extensions with a way to incrementally build a bytes object without multiple allocations and copies. The old pattern was to allocate a large buffer, fill it, then resize -- or use PyBytes_FromStringAndSize on a complete buffer. PyBytesWriter manages a growable buffer internally and produces the final bytes object in one step.

// C extension code (conceptual)
PyBytesWriter *writer = PyBytesWriter_Create(1024);
PyBytesWriter_WriteBytes(writer, header, header_len);
PyBytesWriter_WriteBytes(writer, payload, payload_len);
PyObject *result = PyBytesWriter_Finish(writer);  // single allocation

This is relevant for C extension authors writing serializers, encoders, or protocol packers -- not for Python-level code, where bytearray and bytes.join() serve the same purpose.

What Was Removed or Deprecated in Python 3.15?

  • Old NamedTuple keyword-argument syntax (using NamedTuple("Name", field=type) instead of class form) is removed -- use the class-based form: class Point(NamedTuple): x: int; y: int.
  • Legacy zipimport methods that were already deprecated are removed.
  • collections.abc.ByteString is removed from collections.abc.__all__ (was deprecated since 3.12) -- use Union[bytes, bytearray, memoryview] explicitly.
  • Various undocumented C API functions replaced by the newer stable ABI equivalents.
  • RLock() will silently ignore any positional/keyword arguments -- warnings issued in 3.14 become enforced in 3.15+.

FAQ

Will the UTF-8 default change break my code that reads files without specifying encoding?
Only if those files contain non-UTF-8 characters (Latin-1, Windows CP1252, etc.). If all your files are already UTF-8 (typical for any code written or updated in the last decade), nothing changes. The safest migration: grep your codebase for bare open( calls without encoding= and add explicit encoding arguments where needed.

Is profiling.sampling better than cProfile for all use cases?
Different tools for different jobs. cProfile gives exact call counts and cumulative times -- essential when you need to know exactly how many times a function was called. Sampling gives low-overhead statistical hot-spot detection -- essential in production or when the overhead of instrumentation would change the timing being measured. Use both: sampling to find where time goes, then cProfile to confirm call counts in the hot path.

Does the UTF-8 default apply to sys.stdin and sys.stdout as well?
UTF-8 mode for sys.stdin/stdout/stderr was already available via PYTHONUTF8=1 or python -X utf8 (PEP 540, Python 3.7). PEP 686 extends this to file open() calls. In Python 3.15, both are UTF-8 by default. If you need binary-mode stdin, use sys.stdin.buffer.

Can I use the sampling profiler on production systems without restarting the process?
Yes -- attaching to a running process is one of the stated design goals. The profiler can be triggered via a signal or an out-of-band channel. Sampling at 1 kHz adds under 1% overhead on typical workloads, making it safe for production profiling sessions.

What is the class-based NamedTuple syntax that replaces the deprecated form?
Define it as a class inheriting from NamedTuple with annotated fields: class Point(NamedTuple): x: float; y: float; label: str = "origin". This has been supported since Python 3.6 and is strictly cleaner -- it supports default values, docstrings, and method definitions. The functional form Point = NamedTuple("Point", [("x", float), ("y", float)]) still works and is not deprecated.

Releases In Branch 3.15

VersionRelease date
3.15.0b202 Jun 2026
(8 days ago)
3.15.0b107 May 2026
(1 month ago)
3.15.0a807 Apr 2026
(2 months ago)
3.15.0a710 Mar 2026
(3 months ago)
3.15.0a611 Feb 2026
(3 months ago)
3.15.0a514 Jan 2026
(4 months ago)
3.15.0a413 Jan 2026
(4 months ago)
3.15.0a316 Dec 2025
(5 months ago)
3.15.0a218 Nov 2025
(6 months ago)
3.15.0a114 Oct 2025
(7 months ago)