Python 3.12 New Features — Core Concepts
What changed in Python 3.12
Released in October 2023, Python 3.12 delivered cleaner syntax for generics, removed long-standing f-string limitations, and began the multi-release transition toward removing the GIL. Here are the changes that matter in practice.
New type parameter syntax (PEP 695)
Defining generic types used to require importing TypeVar and creating variables before the class or function:
# Old way
from typing import TypeVar, Generic
T = TypeVar("T")
class Stack(Generic[T]):
def push(self, item: T) -> None: ...
def pop(self) -> T: ...
Python 3.12 introduces built-in syntax:
# New way
class Stack[T]:
def push(self, item: T) -> None: ...
def pop(self) -> T: ...
This also works for functions and type aliases:
def first[T](items: list[T]) -> T:
return items[0]
type Vector = list[float]
type Matrix[T] = list[list[T]]
The type statement creates proper TypeAliasType objects, replacing the ambiguous TypeAlias annotation from 3.10.
F-string grammar overhaul (PEP 701)
F-strings were rewritten to use the PEG parser instead of a custom tokenizer. The practical result: all previous limitations are gone.
# Nested quotes — now works
print(f"{'hello "world"'}")
# Nested f-strings
print(f"result: {f'{x:.2f}'}")
# Backslashes in expressions
print(f"newline: {chr(10)}")
items = ["a", "b", "c"]
print(f"joined: {'\n'.join(items)}")
# Multi-line expressions
result = f"{
some_dict
['key']
}"
# Comments in multi-line f-strings
value = f"{
x # this is the x value
+ y
}"
This matters most for complex string formatting in templates and logging.
Improved TypedDict (PEP 692)
You can now use TypedDict to type **kwargs:
from typing import TypedDict, Unpack
class Options(TypedDict, total=False):
timeout: int
retries: int
verbose: bool
def fetch(url: str, **kwargs: Unpack[Options]) -> str: ...
# Type checker knows valid kwargs
fetch("https://api.example.com", timeout=30, retries=3)
fetch("https://api.example.com", invalid=True) # Error!
@override decorator (PEP 698)
Marks a method as intentionally overriding a parent method:
from typing import override
class Base:
def process(self) -> None: ...
class Child(Base):
@override
def process(self) -> None: ... # OK
@override
def prcess(self) -> None: ... # Type checker error: no matching parent method
Catches typos in method names and accidental signature changes during refactoring.
Per-interpreter GIL (PEP 684)
Each Python sub-interpreter now gets its own GIL. This is mostly a C API change — Python-level code can’t easily use it yet. But it’s the foundation for true parallelism without external processes:
- Sub-interpreters can run on different threads without blocking each other
- Each interpreter has its own module state and GIL
- The
interpretersmodule (still experimental) exposes this at the Python level
This is a stepping stone to 3.13’s free-threaded mode.
Buffer protocol in Python (PEP 688)
Classes can now implement the buffer protocol in pure Python:
class MyBuffer:
def __buffer__(self, flags: int) -> memoryview:
return memoryview(self._data)
Previously, this required C extensions. Now pure Python objects can work with memoryview, struct, and other buffer consumers.
Removals and deprecations
distutilsremoved entirely — usesetuptoolsor modern alternativesasynchat,asyncoreremoved — useasyncioimpmodule removed — useimportlibwstrremoved from Unicode objects — reduces memory by 8 bytes per string
Common misconception
“The type statement is just cosmetic sugar.” It actually creates TypeAliasType objects that type checkers handle differently from plain variable assignments. A type alias is lazily evaluated and has proper scoping — forward references work without quotes. This is a semantic improvement, not just syntax.
The one thing to remember: Python 3.12 cleaned up type hint syntax while starting the most ambitious internal change in CPython’s history — decoupling interpreters from the global lock.
See Also
- Python 310 New Features Python 3.10 gave programmers a shape-sorting machine, friendlier error messages, and cleaner ways to say 'this or that' in type hints.
- Python 311 New Features Python 3.11 made everything faster, error messages smarter, and let you catch several mistakes at once instead of stopping at the first one.
- Python 313 New Features Python 3.13 finally lets multiple tasks run at the same time for real, added a speed booster engine, and gave the interactive prompt a colourful makeover.
- Python Exception Groups Python's ExceptionGroup is like getting one report card that lists every mistake at once instead of stopping at the first one.
- Python Free Threading Nogil Python has always had a rule that only one thing can happen at a time — free threading finally changes that, like opening extra checkout lanes at the grocery store.