What's New in Python 3.15: Lazy Imports, frozendict, and a Fresh Profiling Story
Introduction
Python 3.15 delivers several changes that directly affect how you write, run, and debug Python code.
This release focuses on startup performance with explicit lazy imports, immutable data handling via a new built-in type, and better profiling tools.
Most teams will notice improvements in application startup times and easier performance analysis without heavy overhead.
How Do Explicit Lazy Imports Work in Python 3.15?
Python 3.15 introduces the lazy soft keyword to defer module loading until the imported name is first used.
Write lazy import json or lazy from pathlib import Path at module level. The actual import happens transparently on first access.
This keeps imports tidy at the top of files while avoiding the cost of unused dependencies. In practice, large applications with deep import trees see measurable startup wins.
lazy import json
lazy from pathlib import Path
print("Starting up...") # no loading yet
data = json.loads('{"key": "value"}') # loads here
p = Path(".") # loads here
The feature supports both regular and from-import styles. Exceptions surface at first use, with tracebacks that point to both the access site and the original lazy statement.
Can You Control Lazy Imports Globally or Selectively?
Use the -X lazy_imports command-line option or PYTHON_LAZY_IMPORTS environment variable to set behavior to all, none, or normal.
At runtime, call sys.set_lazy_imports(mode) and sys.get_lazy_imports(). For finer control, supply a filter function via sys.set_lazy_imports_filter().
Most teams will keep the default and add lazy only where it helps. The filter lets you make your own modules lazy while keeping third-party packages eager.
import sys
def myapp_filter(importing, imported, fromlist):
return imported.startswith("myapp.")
sys.set_lazy_imports("all")
sys.set_lazy_imports_filter(myapp_filter)
Lazy imports are restricted to module scope. You cannot use them inside functions, classes, or try blocks. Star imports and future imports also stay forbidden.
What Is the New frozendict Built-in and Why Use It?
Python 3.15 adds frozendict to builtins as an immutable mapping type.
It inherits directly from object, not from dict. A frozendict is hashable when its keys and values are hashable, and it preserves insertion order for iteration while ignoring order in comparisons and hashing.
This matters because many teams already reach for third-party frozen dicts or tuples of pairs. Now the language provides a clean, built-in option that integrates with copy, json, pickle, and other modules.
a = frozendict(x=1, y=2)
b = frozendict(y=2, x=1)
print(a == b) # True
print(hash(a) == hash(b)) # True
# a['z'] = 3 raises TypeError
Several standard library modules now accept frozendict where they previously expected mappings.
How Does the New Profiling Package Change Performance Work?
Python 3.15 introduces a dedicated profiling module that organizes profiling tools.
cProfile moves to profiling.tracing (with the old name kept as an alias), and the profile module is deprecated.
The headline addition is Tachyon, a high-frequency statistical sampling profiler in profiling.sampling. It runs with near-zero overhead and supports sampling rates up to one million hertz.
What Can You Do with the Tachyon Sampling Profiler?
Tachyon lets you attach to a running process by PID, profile scripts or modules, and choose wall-clock, CPU, GIL-holding, or exception modes.
It understands threads and async code, and outputs in multiple formats including pstats, collapsed stacks, flamegraphs, and heatmaps. You can also run a live TUI.
In practice, this makes production profiling far more practical than before. Teams can now gather data with minimal impact instead of relying solely on tracing profilers that slow code down.
How Has the JIT Compiler Improved in Python 3.15?
The experimental JIT received a significant upgrade, including LLVM 21, a new tracing frontend, basic register allocation, and more optimizations.
It now handles more bytecode instructions, object creation, overloaded operations, and generators. Reported speedups reach 6-7% on x86-64 Linux and 12-13% on AArch64 macOS in some workloads.
Most teams will not enable the JIT in production yet, but the direction is clear: better runtime performance is coming.
What Changed with Default Encoding and Error Messages?
open() and many I/O operations now default to UTF-8. You can opt out with PYTHONUTF8=0 or -X utf8=0. The encoding='locale' option remains for system locale behavior.
Error messages received small but useful polish. AttributeError suggestions now consider attributes accessed via members, and delattr() offers closer matches on failure. Unraisable exceptions appear highlighted by default.
These changes reduce friction in daily work and make tracebacks easier to read.
What Other Library and Core Improvements Stand Out?
Several modules received updates. The math module gained integer submodule plus functions like isnormal(), issubnormal(), fmax(), fmin(), and signbit().
bytearray adds take_bytes() for zero-copy extraction. Unpacking with * and ** now works inside comprehensions and generator expressions.
The slice type supports subscription and becomes generic. memoryview handles more complex float and double types.
Summary Table
| Area | Key Change | Benefit |
|---|---|---|
| Imports | lazy keyword (PEP 810) |
Faster startup for large apps |
| Data Types | Built-in frozendict (PEP 814) |
Native immutable mappings |
| Profiling | profiling module + Tachyon sampler (PEP 799) |
Low-overhead performance insights |
| Performance | Upgraded JIT compiler | Better runtime speed in supported cases |
| Encoding | UTF-8 default for I/O | Consistent modern behavior |
| Error Handling | Improved AttributeError suggestions | Faster debugging |
| Comprehensions | * and ** unpacking support (PEP 798) | Cleaner flattening of iterables |
Conclusion
Python 3.15 refines the language in areas that affect real development work: startup time, data immutability, profiling, and everyday usability.
Start experimenting with lazy imports and the new profiling tools on non-critical code. The changes are stable enough for testing, and many will become part of standard workflows over time.