r/Python 5d ago

News Python 3.15 Release Highlights

Python 3.15 has reached Release Candidate 1, and the final release is expected on October 1, 2026.

I went through the changes and tried to summarize the ones that seem most relevant for everyday Python development without going through all the PEP numbers.

- Faster startup with lazy imports

Python 3.15 introduces lazy imports, which means some modules can be loaded only when they are actually needed instead of being loaded immediately.

This could help applications and CLI tools that spend a noticeable amount of time importing modules before doing any actual work.

- UTF-8 becomes the default

UTF-8 is becoming the default encoding, which should reduce encoding-related problems, especially when code is running on different operating systems.

This should make situations where something works on one computer but fails because of a different system encoding less common.

- Built-in immutable dictionaries

Python 3.15 adds a built-in "frozendict"-style immutable mapping.

Similar to how a tuple provides an immutable alternative to a list, this gives Python developers a standard way to work with dictionaries that cannot be modified.

- JIT improvements

The JIT compiler continues to improve in Python 3.15.

Early benchmarks show performance improvements in some workloads, including roughly 8–9% on Linux and larger improvements in some Apple Silicon tests.

These numbers will obviously depend on the workload, so I would wait for more benchmarks before making broader conclusions.

- New profiler: Tachyon

Python 3.15 also introduces Tachyon, a new sampling profiler designed for very low overhead.

It can sample running programs at very high frequencies and can also be attached to an already-running process.

This could be useful for finding performance problems without having to restart an application with a profiler attached from the beginning.

- Some terminal improvements

The interactive Python experience is getting a few smaller improvements, including colored error messages and prompts.

The "sqlite3" command-line interface also gets SQL keyword completion.

- Free-threaded Python is still opt-in

Python 3.15 does not make the no-GIL/free-threaded build the default.

It is still something you have to explicitly use.

However, free-threaded Python is becoming more mature, including improvements to ABI support that should make it easier for C extension developers to support it.

Overall, Python 3.15 doesn't look like a release that completely changes how Python is written. Most of the changes are focused on performance, tooling, and improving some long-standing parts of the language.

Since this is already RC1, the major feature set should be mostly locked and the remaining work should mainly be bug fixes and final polishing.

Has anyone here been testing the Python 3.15 beta or RC?

I'm especially interested in whether lazy imports have caused compatibility problems with existing projects.

333 Upvotes

72 comments sorted by

View all comments

33

u/Significant_Map_19 5d ago

lazy imports are going to break so many frameworks that do weird metaprogramming stuff at startup, I can already feel the bug reports piling up

the frozendict thing is nice though, been using dicts as immutable keys with wrappers for years

49

u/SCD_minecraft 5d ago

It's opt in

import foo # nothing changes, just as before

lazy import bar # acually loaded only when needed

4

u/Rodot github.com/tardis-sn 5d ago

This does make me wonder if lazy may at some point become more general. Could we one day see things like the following?

```

lazy result_a = f(x)

lazy result_b = g(y)

if x > y:

    return result_a

elif x > result_b:

    return result_b

```

8

u/ProtectionOne9478 5d ago

You can do this now with async.

4

u/zurtex 5d ago

Or just lambdas, if you don't mind recalculating:

result_a = lambda: f(x)
result_b = lambda: g(y)

if x > y:
    return result_a()
elif x > result_b():
    return result_b()

Or cache and partial if you do mind reclalculating:

from functools import cache, partial

result_a = cache(partial(f, x))
result_b = cache(partial(g, y))

if x > y:
    return result_a()
elif x > result_b():
    return result_b()

People see the word "lazy" and forget that's what a function basically is.

3

u/ProtectionOne9478 5d ago

yep, those work too. i use async a lot so it was just the first i thought of.

partial is probably the best way, since you don't have to be in an async context and changes to x won't change the behavior of the function like it will for the lambda, eg you'll get 2 for a() in the following code which could lead to unexpected behavior:

x = 1
a = lambda: x
x = 2
a()

1

u/RingularCirc 3d ago edited 3d ago

A generic (and perfectly-typable) Lazy[T] class is also easy to write once and for all (though for a thread-safe one it'll require adding locking and stuff). Something like:

class Lazy[T]:
    def __init__(self, computation: Callable[[], T]) -> None:
        self._f: Callable[[], T] | None = computation
        self._val: T | None = None
    @property
    def value(self) -> T:
        if self._f:
            self._val = self._f()
            self._f = None
        return self._val

I hope it's correct (I've written it once but can't find quickly enough); bubbling up an exception from _f() when lazy.value is accessed is the intended behavior, no need to stow it away as some version of caching do because we won't get to erasing _f in this case and will try the computation once more later... which... well, I'm not sure now but it's no worse than second best choice anyway.

Oh yeah typecheckers will still be mad at this code, most probably. But IMO in such a small self-contained class we can safely use # type: ignore at return. There's a good solution to typing here if Lazy stores a _x: C | V where C holds the callable, V holds the value and both are provably disjoint, for example C = tuple[Literal[True], Callable[[], T]] and V = tuple[Literal[False], T]. Even not as cumbersome to annotate as I expected (and the code is a simple if self._x[0]: ....

EDIT: For convenience this can also accept arguments for the callable in __init__ and pack it all in a partial, yeah, to allow the caller not to bother.

3

u/SCD_minecraft 5d ago

Questionable

imports aren't really ment to have side effects, while function have side effects quite often

1

u/Brian 5d ago

I remember early versions of pypy had an experimental feature like that, where you could have a thunk object space where access to the value would trigger it to become the result of the evaluation. I kind of doubt it'll ever be added to python though - laziness allows some cool stuff, but there's lots of potential for bugs and weirdness with it.

1

u/hotsauce56 5d ago

They talked about it a bit on Core.py podcast. There’s technically already plumbing for it with the lambda keyword