Mateen Kiani
Published on Thu Jul 31 2025·4 min read
When you think of exception handling in Python, the try/except
duo probably comes to mind. But did you know you can use try
without an except
clause? This pattern leverages else
and finally
to separate success and cleanup logic, offering clearer control flow and fewer hidden pitfalls. How can you structure error handling so that success paths and cleanup run smoothly without catching every exception in the same block?
By combining try
with else
and finally
, you can keep error-handling code distinct from normal execution and cleanup logic. The else
block runs only when no exception occurs, while finally
always runs—perfect for releasing resources. Understanding this pattern helps you write more readable, maintainable code and prevents accidentally swallowing unexpected errors.
Using try
alone might seem odd at first, but it offers two main advantages:
except
, unexpected errors bubble up, preventing silent failures.try:connection = open_database()except DatabaseError:handle_db_error()else:# Only runs if no exception occurredprocess_data(connection)finally:# Always runsconnection.close()
In this example, we only catch DatabaseError
in except
(if we had one), run normal logic in else
, and always close the connection in finally
. If another error happens—say a network glitch—you’ll notice it immediately instead of hiding it.
The else
clause runs only when the try
block succeeds. Use it to keep your main logic separate from the setup that might fail.
try:file = open("config.json")except OSError:print("Config file missing.")else:data = json.load(file)print("Loaded config:", data)finally:file.close()
This makes it clear:
Tip: Use
else
to guard code that shouldn’t run if setup fails.
Regardless of success or failure, some tasks must always happen: closing files, releasing locks, or ending transactions. Put them in finally
.
lock.acquire()try:critical_section()finally:lock.release()
Here, you don’t need an except
if you’re not handling errors specifically. Any exception from critical_section()
bubbles up, but the lock is always released.
Sometimes you want to detect a condition in else
and raise your own exception with a clear message. This can be more informative than letting Python’s default errors surface.
try:value = int(user_input)else:if value < 0:raise ValueError("Negative numbers not allowed")finally:print("Input processing done.")
For more on crafting custom error messages, check out raising exceptions with custom messages in Python.
except
if you’re not planning to recover from that error.FileNotFoundError
, not a bare except:
.try
blocks small: Limit the code inside try
to only the operations that might fail.else
and finally
.Avoid using a bare
except:
. Let unexpected errors bubble up and be addressed in higher-level logic or logs.
import requeststry:response = requests.get("https://api.example.com/data")response.raise_for_status()else:data = response.json()print(f"Received {len(data)} items.")finally:print("HTTP request complete.")
.raise_for_status()
inside the try
so HTTP errors become exceptions.else
, we assume the request succeeded and parse JSON.finally
, we log completion regardless of outcome.Using try
without except
may seem unconventional, but it clarifies your program’s structure by cleanly separating error handling, successful execution, and cleanup. The else
block runs only when no exception occurs, and finally
guarantees resource release. When you catch exceptions, focus on those you can handle. Let the rest bubble up and be logged or handled elsewhere.
Next time you find yourself writing a broad try/except
just to close a file or release a lock, remember: break it into else
and finally
. You’ll end up with code that’s safer, easier to read, and less prone to hidden bugs.