Python 3.12.0rc1 landed this week, marking the final stretch before the October release. Having tracked Python’s evolution for most of my career, this release feels like a particularly significant one. Not because of any single headline feature, but because several long-running efforts are converging in ways that matter for real-world Python development.
Let me walk through what’s actually changing and why it matters for your projects.
Per-Interpreter GIL: The Beginning of the End#
The biggest technical story in Python 3.12 isn’t a user-facing feature — it’s PEP 684, which introduces per-interpreter GIL support. For those who haven’t followed the saga, the Global Interpreter Lock (GIL) has been Python’s most famous limitation for decades. It prevents true parallel execution of Python bytecode across threads, which is why CPU-bound Python programs don’t scale across cores.
PEP 684 doesn’t remove the GIL. What it does is allow each sub-interpreter to have its own GIL, meaning multiple interpreters running in the same process can execute Python code truly in parallel. This is the foundation for PEP 554 (multiple interpreters in the stdlib), which isn’t in 3.12 but is coming.
The practical impact for 3.12 is limited — the C API for per-interpreter GIL exists, but the Python-level interface isn’t ready yet. Think of this as laying the plumbing. But the direction is clear: Python is systematically addressing its concurrency limitations without breaking backward compatibility. Combined with Sam Gross’s PEP 703 for an optional GIL-free build (which the steering council is evaluating), the Python concurrency story is about to change significantly.
Improved Error Messages Continue#
Python 3.10 introduced better error messages, and 3.11 expanded on them. Python 3.12 pushes even further. The improvements are targeted and practical:
- Modules that shadow standard library modules now get helpful suggestions when imports fail. If you’ve got a file called
random.pyin your project andimport randombreaks, Python now tells you why - NameError suggestions now cover more cases, including suggesting
self.xwhen you forgetselfin method bodies - ImportError messages for
from module import namenow suggest similar names from the module
These seem small, but they compound. I work with teams where junior developers lose significant time to cryptic error messages. Every improvement here reduces friction. I’ve seen the shadow module issue alone cost new Python developers hours of confusion — having the interpreter just tell you what’s wrong is a meaningful quality-of-life improvement.
Type System Enhancements#
Python’s gradual typing story continues to evolve. 3.12 introduces PEP 695, which provides new syntax for type parameter declarations:
# Old way
from typing import TypeVar
T = TypeVar('T')
def first(lst: list[T]) -> T:
return lst[0]
# New 3.12 way
def first[T](lst: list[T]) -> T:
return lst[0]The new syntax is cleaner and more intuitive, especially for developers coming from TypeScript or Rust where generic syntax is similar. Type aliases also get a dedicated keyword:
type Vector = list[float]
type Matrix[T] = list[list[T]]For teams that have adopted type hints — and you should — this reduces boilerplate and makes generic code more readable. The typing module has been accumulating syntax debt as it evolved from a third-party library to a core language feature, and PEP 695 is a significant step toward cleaning that up.
Performance: The Specializing Interpreter Matures#
Python 3.11 introduced the specializing adaptive interpreter, which optimizes frequently-executed bytecode instructions based on the types they encounter. Python 3.12 builds on this with additional specialization opportunities and better comprehension performance.
Comprehensions now use inline bytecode rather than creating a hidden nested function, which reduces overhead for list, dict, and set comprehensions. This is a common Python pattern, and making it faster benefits virtually every Python codebase.
The overall performance improvement over 3.11 is more modest than the dramatic 3.10→3.11 jump (which was 10-60% faster). But the cumulative effect of 3.10→3.11→3.12 is substantial. If you’re still on 3.9 or 3.10, upgrading to 3.12 should deliver measurable speedups for most workloads.
Removed Features and Breaking Changes#
Python 3.12 removes several deprecated features that have been on notice for years. The biggest ones:
distutilsis gone: Fully removed after being deprecated since 3.10. If you haven’t migrated your build tooling tosetuptoolsor another build backend, this is your forcing function- Old
typingaliases: Several deprecated typing constructs are removed asynchat,asyncore, andimp: Long-deprecated modules finally disappear
The distutils removal will break some older packages and build scripts. If you maintain internal packages, test your builds against 3.12rc1 now rather than discovering issues after the October release.
My Take#
Python’s trajectory under its current development model is steady and positive. The language isn’t making dramatic leaps — it’s systematically addressing known pain points while maintaining backward compatibility. That’s exactly what a mature language with Python’s install base should be doing.
The per-interpreter GIL work is the most strategically important change, even though its practical impact in 3.12 is minimal. It signals that the core team is serious about making Python a viable choice for CPU-parallel workloads — something that’s been a perennial complaint and a common reason teams reach for Go or Rust.
For production teams, I’d recommend testing against the RC now and planning a 3.12 upgrade for Q1 2024 (after the initial patch releases stabilize). The combination of performance improvements, better error messages, and cleaner type syntax makes it a worthwhile upgrade for any Python shop.
The language keeps getting better in the ways that matter. That’s worth celebrating.
