Python 3.12: F-String Improvements and GIL Steps
python dev
Python 3.12 was released in October 2023, continuing the momentum from 3.11’s performance gains. Here’s what matters.
F-String Improvements
Nested Expressions
You can now nest f-strings without escaping:
# Before 3.12 - had to use different quotes
name = "Alice"
result = f"User said: '{f\"Hello, {name}\"}'" # Awkward
# Python 3.12 - reuse quotes freely
result = f"User said: "{f"Hello, {name}"}" # Clean
Multi-line Expressions
# Python 3.12 - multi-line expressions in f-strings
query = f"""
SELECT *
FROM users
WHERE name = {
user.name
if user.name is not None
else 'Anonymous'
}
"""
Comments Inside F-Strings
# Python 3.12
data = f"{
x # This is x
+ y # Plus y
}"
Backslashes Allowed
# Python 3.12
escaped = f"Path: {path.replace("\\", "/")}" # Now allowed
Better Error Messages
Python 3.12 continues improving error messaging:
# More specific errors
lst = [1, 2, 3]
lst[10] # IndexError: list index out of range. The list has only 3 items.
# Better suggestions
import colections
# Did you mean 'collections'?
Type System Enhancements
TypedDict With Defaults
from typing import TypedDict, NotRequired
class User(TypedDict):
name: str
email: str
phone: NotRequired[str] # Optional, won't error if missing
Override Decorator
from typing import override
class Parent:
def process(self) -> int:
return 0
class Child(Parent):
@override # Type checker verifies this actually overrides
def process(self) -> int:
return 1
@override # Error! 'handle' doesn't exist in parent
def handle(self) -> int:
return 2
Generic Type Alias Syntax
# Python 3.12
type Point = tuple[float, float]
type Handler[T] = Callable[[T], None]
type Matrix[T] = list[list[T]]
# Instead of
from typing import TypeAlias
Point: TypeAlias = tuple[float, float]
Per-Interpreter GIL (PEP 684)
The big architectural change:
# Previously: One GIL for entire Python process
# Python 3.12: Each interpreter can have its own GIL
import interpreters # Experimental
interp = interpreters.create()
# This interpreter has its own GIL
# True parallelism possible across interpreters
Still experimental, but foundational for Python’s parallel future.
Memory Improvements
Smaller Object Sizes
# Objects use less memory in 3.12
import sys
d = {"a": 1, "b": 2, "c": 3}
sys.getsizeof(d) # Smaller than 3.11
Immortal Objects
Certain objects never need reference counting:
# None, True, False are immortal in 3.12
# Reduces cache coherence overhead in multicore
Workflow Changes
pathlib Improvements
from pathlib import Path
# New walk method
for root, dirs, files in Path(".").walk():
for file in files:
print(root / file)
# New with_segments method
p = Path("/usr/local/bin")
p.with_segments("var", "log") # Path('/var/log')
itertools Additions
from itertools import batched
# Split into batches
list(batched("ABCDEFG", 3))
# [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]
Improved sqlite3
import sqlite3
# Command-line tool improvements
python -m sqlite3 database.db
# Better type handling
conn = sqlite3.connect(":memory:", autocommit=True) # New parameter
Deprecations
Deprecated
# asyncio.get_event_loop() without running loop → deprecated
# Use asyncio.get_running_loop() instead
# datetime.utcnow() and utcfromtimestamp() → deprecated
# Use datetime.now(UTC) instead
from datetime import datetime, timezone
now = datetime.now(timezone.utc) # Correct way
Removed
# distutils is gone
# Use setuptools or build instead
# From setuptools
from setuptools import setup
Performance
Continued gains from 3.11:
| Task | 3.10 | 3.11 | 3.12 |
|---|---|---|---|
| Startup | 1.0x | 0.9x | 0.85x |
| Regular code | 1.0x | 1.25x | 1.30x |
| Frame creation | 1.0x | 2.0x | 2.2x |
Improvements are cumulative—upgrade path matters.
Upgrading
Compatibility Check
python -Werror -c "import your_project"
# Run with deprecation warnings as errors
Common Issues
# Removed distutils
# Before
from distutils.core import setup
# After
from setuptools import setup
# datetime changes
# Before
from datetime import datetime
now = datetime.utcnow()
# After
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
Test Your Code
# Run tests with Python 3.12
python3.12 -m pytest
# Check for deprecation warnings
python3.12 -W default your_script.py
Should You Upgrade?
Yes, if:
- You want improved f-strings
- You need better type annotations
- Performance improvements help
- You’re starting a new project
Wait, if:
- Dependencies don’t support 3.12 yet
- You have extensive distutils usage
- Stability is more important than features
Final Thoughts
Python 3.12 is a solid release. The f-string improvements alone make code cleaner. The per-interpreter GIL is the start of something bigger—true Python parallelism is on the roadmap.
Upgrade when your dependencies allow.
Python 3.12: Better syntax, better types, better future.