Still Using try-except This Way? Here's a Cleaner Pattern!
Say goodbye to bloated try-except blocks — and hello to cleaner, more Pythonic error handling.

Catching exceptions doesn’t have to make your code look like a mess.
Still Using try-except
This Way? Here's a Cleaner Pattern!
If you’ve been writing Python for a while, you’ve probably developed a reflex for wrapping risky code in a try-except
block. Maybe something like this:
try:
value = int(user_input)
except ValueError:
value = None
It works. It’s functional. But is it elegant? Not quite.
Let’s explore a cleaner, more Pythonic way to handle exceptions — one that’s easier to read, scales better, and aligns with the “ask for forgiveness, not permission” philosophy — but without overusing try-except
blocks like duct tape.
The Problem With Overused try-except
There’s nothing wrong with exception handling itself. Python encourages it. The problem arises when try-except
is used as a blunt instrument—cluttering logic, masking bugs, or being applied too broadly.
Take this common example:
try:
data = json.loads(input_str)
except:
data = {}
This is dangerous. It catches everything, including KeyboardInterrupt
, MemoryError
, and other exceptions you probably don’t want to ignore.
Or this one:
try:
os.remove(file_path)
except:
pass
Now you’ve got a silent failure — and no idea why the file wasn’t deleted.
In short: try-except
should be precise, not lazy.
A Cleaner, Safer Pattern: Use Helper Functions
Instead of sprawling try-except
blocks, consider using small, intention-revealing functions to encapsulate exception-prone behavior.
Let’s revisit the earlier example:
Instead of:
try:
value = int(user_input)
except ValueError:
value = None
Use:
def safe_int(value):
try:
return int(value)
except ValueError:
return None
value = safe_int(user_input)
This tiny abstraction instantly makes your main logic cleaner, easier to test, and more readable.
And it scales. If your exception handling becomes more complex later, it’s isolated to one place:
def safe_int(value, default=None):
try:
return int(value)
except (ValueError, TypeError):
return default
Keep try-except
Blocks Narrow and Intentional
Another way to clean up exception handling is to make your try
blocks as narrow as possible. Only wrap the specific line(s) that might fail:
Messy:
try:
connect_to_db()
query = build_query()
results = run_query(query)
except Exception as e:
log_error(e)
raise
Cleaner:
connect_to_db()
query = build_query()
try:
results = run_query(query)
except QueryExecutionError as e:
log_error(e)
raise
This makes it immediately obvious which line could raise the exception, and you avoid accidentally swallowing bugs from unrelated code.
Pattern: Use Context Managers for Resource Handling
When dealing with file operations, network connections, or locks, it’s tempting to use try-finally
to clean up:
f = open('data.txt')
try:
process(f)
finally:
f.close()
But there’s a more Pythonic way: context managers.
Better:
with open('data.txt') as f:
process(f)
This not only handles exceptions gracefully, but also makes the code shorter, safer, and easier to understand.
Bonus: Use contextlib.suppress
for Known, Harmless Exceptions
Sometimes, you’re okay with ignoring a specific error. Python gives you a clean way to do that:
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('temp.txt')
This is much more readable (and safer) than a bare except
or pass
.
Final Thoughts
try-except
is a powerful tool—but like any power tool, it’s most effective when used with precision.
The next time you reach for a try-except
block, ask yourself: can I encapsulate this? Can I clarify the intent? Can I avoid masking other problems?
Because clean code isn’t just about making things work — it’s about making them obvious, maintainable, and elegant.
Happy coding 🐍
If you enjoyed this, follow me for more clean code tips and Python best practices. Or better yet — share this with a friend who’s still misusing try-except
😉
