I Rewrote My Python Code Using Modern Python 3.12+ — Here’s What Changed
I thought my Python code was already clean and efficient… until I rewrote it using Python 3.12 and beyond. The result? Fewer lines, better…

You Won’t Believe What You’re Missing in Python Until You Upgrade
I Rewrote My Python Code Using Modern Python 3.12+ — Here’s What Changed
I thought my Python code was already clean and efficient… until I rewrote it using Python 3.12 and beyond. The result? Fewer lines, better readability, and smarter performance.
Python has come a long way from the early 3.x days. Over the past few versions, the language has evolved not just with syntax improvements, but with a philosophy shift — more expressive, more efficient, and sometimes even a bit more functional.
When Python 3.12 dropped, I decided to take a chunk of legacy code I wrote back in 3.6 and rewrite it using all the modern goodness now available in 3.10, 3.11, and 3.12.
I didn’t expect a total transformation.
But that’s exactly what happened.
In this article, I’ll walk you through:
- What changed in my code
- What new features I used
- Why these changes made a real difference
- What you should consider refactoring in your own codebase
Let’s jump in.
Pattern Matching Made My If-Else Blocks Vanish
Before:
def handle_event(event):
if event['type'] == 'click':
return handle_click(event)
elif event['type'] == 'hover':
return handle_hover(event)
else:
return handle_default(event)
After (Python 3.10+):
def handle_event(event):
match event:
case {'type': 'click'}:
return handle_click(event)
case {'type': 'hover'}:
return handle_hover(event)
case _:
return handle_default(event)
Easier to read — no more nestedif
/elif
Structured matching means fewer bugs
More declarative and expressive
Pattern matching isn’t just sugar — it fundamentally changes how you write decision logic, especially when dealing with dictionaries or nested data.
except*
in Python 3.11 Helped Me Handle Parallel Exceptions Gracefully
If you’re using threads, asyncio, or concurrent tasks, you’ve probably hit situations where multiple exceptions are raised — and only the first one gets shown.
Now with except*
, you can handle multiple exceptions separately.
async def main():
try:
await asyncio.gather(task1(), task2())
except* ValueError as ve_group:
for ve in ve_group.exceptions:
log(ve)
This is huge for concurrent code. It lets you reason about multiple failures without swallowing important errors.
F-strings Got Smarter — And I’m Not Going Back
Python 3.12 finally introduced f-string debugging with expressions inside curly braces. This means you can now do:
value = 42
print(f"{value=}") # Prints: value=42
It’s such a small change, but during debugging, it becomes an essential tool. No more concatenating variable names manually — Python does it for you.
Type Hints Went From Optional to Essential
My old codebase used types sporadically, mostly as comments. Today, using features like |
(introduced in Python 3.10) and Self
(in 3.11), I rewrote function signatures like this:
Before:
def get_data(user: Union[str, None]) -> Union[str, None]:
After:
def get_data(user: str | None) -> str | None:
And with Self
:
class Config:
def set(self, key: str, value: Any) -> Self:
self.data[key] = value
return self
This isn’t just cleaner — it’s also more maintainable. Tools like mypy
, pyright
, and even your IDE can now catch issues before runtime.
tomllib
Replaced Yet Another External Dependency
Before Python 3.11, if you wanted to parse a pyproject.toml
, you had to install toml
or tomli
.
Now:
import tomllib
with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
Less boilerplate. One less dependency. And it’s native.
Performance Gains Are Built Into the Language
While refactoring, I also noticed:
Function calls were faster thanks to 3.11’s optimizations
List comprehensions performed better
The overall interpreter speed improved by up to 25%
Even without changing your code, upgrading to Python 3.12 gives you free performance wins.
Soft Keywords Made Syntax Extensions Easier to Read
Python 3.10+ introduced soft keywords, which means Python now supports keywords that don’t conflict with variable names — opening doors to more flexible syntax.
match command:
case 'start':
start()
case 'stop':
stop()
No need to worry about match
being a reserved keyword for years to come. It’s context-aware — a subtle, yet forward-thinking change in the language’s design.
Better Error Messages Changed How I Debug
Python 3.11+ has dramatically improved tracebacks.
Example:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
--> in line 34: total = price + "5"
You now get precise file locations, code snippets, and contextual hints. It’s like having a linter baked into your exceptions.
I now fix bugs faster — and spend less time deciphering vague errors.
Conclusion: Upgrade Your Mindset, Not Just Your Python Version
What started as a quick refactor turned into a full-on mindset shift. Python today isn’t the same language it was just a few versions ago.
If you’re:
- Still writing long
if-else
blocks - Avoiding type hints
- Installing external packages for basic tasks
- Not using
match
,Self
, or f-string debugging…
…it might be time to rethink how you write Python.
Refactoring my old code wasn’t just about using the newest shiny features — it was about embracing Python the way it was meant to be used in 2025.
Final Thoughts
Next time you open an old script, ask yourself:
“How would I write this today — using the best tools Python has to offer?”
Odds are, your answer will lead you to cleaner, faster, and more Pythonic code.
If you found this helpful, follow me for more Python deep dives, refactoring tips, and software engineering insights. Let’s write better code — together.
