October 2023, Revisited: Python 3.12 and the Quiet Work Before the GIL Fell
Eric Greene June 11, 2026Our Three-Year Retrospective reaches October 2023. Python 3.12 arrived on October 2, right on its annual cadence, and at first glance it looked like a modest release: nicer f-strings, friendlier errors, some speed. But 3.12 was also the release where Python's concurrency future first became visible in shipping code — and the release that finally pulled a band-aid the packaging ecosystem had been picking at for years.
f-strings become real expressions
PEP 701 was the crowd-pleaser. Before 3.12, f-strings were parsed by a separate, hand-rolled mechanism with arbitrary-feeling restrictions: you could not reuse the enclosing quote character inside the braces, could not split expressions across lines, could not include backslashes. Everyone had hit f"{data["key"]}" and sighed.
Python 3.12 moved f-string parsing into the PEG grammar itself, and the restrictions simply vanished. Quote reuse worked, multiline expressions worked, comments inside braces worked, and — because the parser now understood f-strings properly — error messages inside them pointed at the actual problem. It was a small change that removed a hundred daily paper cuts, and it exemplified the release's character: not new capability, but the removal of friction nobody should have had to think about.
data = {"name": "Ada", "scores": [99, 87, 95]}
# Reusing the enclosing quote type inside the braces — a SyntaxError before 3.12
print(f"Top score for {data["name"]}: {max(data["scores"])}")
# Multi-line expressions, with comments, now work too
report = f"Average: {
sum(data["scores"]) / len(data["scores"]) # arithmetic mean
}"The error-message improvements continued the streak begun in 3.10: 3.12 added suggestions like "Did you forget to import 'sys'?" and caught the classic missed-self with "Did you mean: 'self.attribute'?". For anyone teaching Python — our day job — these changes measurably shortened the time beginners spent stuck.
The GIL work hiding in plain sight
The deep story was PEP 684, a per-interpreter GIL. Python had long supported multiple interpreters in one process, but they shared a single Global Interpreter Lock, making the feature useless for parallelism. In 3.12, each subinterpreter could own its own GIL, meaning true multi-core parallel execution of Python code in one process — no multiprocessing serialization overhead, no separate process memory.
The catch: in 3.12 this was exposed only through the C API. There was no interpreters module for Python code yet; that was promised for later. So almost nobody used it directly. But the timing made it electric anyway — just weeks earlier, in July 2023, the steering council had announced its intent to accept PEP 703, the proposal to make the GIL itself optional. October 2023 was the month working Python developers first held a release in their hands and could see, concretely, that the GIL era was actually going to end, one way or another.
Alongside this came honest engineering wins: comprehensions were inlined (PEP 709), isinstance checks against protocols got dramatically faster, and PEP 695 gave generics a clean native syntax — def first[T](items: list[T]) -> T — replacing the TypeVar boilerplate.
The distutils funeral
Python 3.12 also removed distutils after decades of service and years of deprecation warnings (PEP 632). The removal broke a long tail of older build scripts and CI images that had ignored the warnings, and October 2023 saw the predictable flurry of pinned versions and hasty setuptools migrations. Painful, but necessary: the packaging ecosystem could not modernize around a deprecated standard-library module that every tool had to tiptoe around. In retrospect it marked the moment Python packaging stopped apologizing and started consolidating.
Looking back from June 2026
3.12 turned out to be exactly what it looked like: a transition release, and a good one. The free-threaded build arrived experimentally in 3.13 and matured from there, and the per-interpreter GIL groundwork laid here is part of why that landing kept getting smoother. PEP 695 syntax is now just how Python generics look, the f-string restrictions are a trivia question, and 3.12 itself settled into a long, stable run as one of the most widely deployed versions in the 3.x line.
Everything in this release is now simply "Python" to someone learning the language today, which is how we teach it: Introduction to Python starts learners on modern syntax and modern error messages from day one, and Intermediate Python digs into the type system, the concurrency model, and the engine-level changes this release set in motion.