Try and except are the building blocks of exception handling in Python. You’ll also sometimes see finally
and else
. Here’s the summary:
try
: run potentially-error-raising code in hereexcept
: catches and handles errors that might have occurred in thetry
finally
: always runs after try / except, even if there were returns or re-raiseselse
: runs if try did not raise an error and try did notreturn
Try and Except
A simple try-except block looks like this:
try:
risky_thing()
except Exception as e:
print(f"oh no! exception: {e}")
You’ll usually see try and except by themselves; finally
is used less often, and else
even less. Here’s a (slightly) more realistic example:
me = {"name": "justin"}
def get_age(person):
try:
return person["age"]
except KeyError as e:
print(f"caught key error: {e}")
# There's no 'age' key on the dict
get_age(me)
# caught key error: 'age'
Except blocks optionally accept a specific error type—the example above will only catch a KeyError
. In practice, you should always specify error type. If your code could produce multiple types of errors, just add an additional except block for each type:
try:
dangerous_code()
except TypeError as e:
# handle error
except KeyError as e:
# handle error
except IndexError as e:
# handle error
Or group your exception handlers with parentheses and commas:
try:
dangerous_code()
except (KeyError, IndexError) as e:
# handle these two errors
except (ValueError, TypeError) as e:
# handle these two errors
This way you know exactly how your code failed, and can log or fix it appropriately.
However, if you just need to ensure your code won’t blow up and you don’t care what kind of exception was raised, you can catch the Exception
class:
try:
dangerous_code()
except Exception as e:
print(f"hit an error: {e}")
All non-fatal Python exception classes inherit from Exception
, so you’ll catch almost any exception this way.
me = {"name": "justin", "age": 100}
def get_age(person):
try:
print("Getting age")
return person["age"]
except KeyError as e:
print("key error hit")
finally:
print("finally block hit")
my_age = get_age(me)
# Getting age
# finally block hit
print(my_age)
# 100
Finally
Finally executes code at the end of your try-except block and is typically used to perform some kind of cleanup action, like ensuring a file was closed1. Finally will run whether or not there was an exception, even if the exception was unhandled. Said another way, finally
will always run:
me = {"name": "justin", "age": 100}
def get_age(person):
try:
print("Getting age")
return person["age"]
except KeyError as e:
print("key error hit")
finally:
print("finally block hit")
my_age = get_age(me)
# Getting age
# finally block hit
print(my_age)
# 100
You might notice above that the finally
block printed even though it’s down below a return
statement. That’s unusual in Python; usually return
exits the function before anything below it can run. Finally is an exception to this pattern, it will always run. To really drive this point home, let’s raise an unhandled exception:
# Don't do this
def bad_method():
try:
print("Starting")
raise TypeError("AHHH")
except KeyError as e:
print("this won't hit, wrong error type")
finally:
print("the finally")
bad_method()
# Starting
# the finally
# TypeError: AHHH
Notice above that the raise
still happened; bad_method()
still raised the error, but it did it after the finally block executed.
One other important note about finally
: if a finally clause includes a return
statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement2:
me = {"name": "justin", "age": 100}
def get_age(person):
try:
print("Getting age")
return person["age"]
except KeyError as e:
print("key error hit")
finally:
print("finally block hit")
return "hello"
my_age = get_age(me)
# Getting age
# finally block hit
print(my_age)
# hello
In the example above my_age
is “hello”, despite the return statement in the try block which should have returned 100
. That’s because a return
statement inside a finally
always takes precedence. If the finally does not contain a return statement the function will use the return value from the try
or except
blocks.
A
return
statement within afinally
block is always the value returned from its function, even if there’s an unhandled exception in its try / except blocks. For that reason you should neverreturn
from within a finally block—you could accidentally swallow exceptions without even knowing it.
Else
Within try / except, an else block is used to evaluate code only if the try block did not raise
an exception or return
:
def else_example1()
try:
print("no errors here")
except Exception as e:
print("this won't print")
else:
print("this will print")
else_example1()
# no errors here
# this will print
def else_example2()
try:
print("no errors here")
return "hi"
except Exception as e:
print("this won't print")
else:
print("this will also not print")
else_example2()
# no errors here
"hi"
I rarely see else
used in this context, and it might indicate a code smell—in many cases you can accomplish the same thing through appropriate use of raise
.
Helpful Links
- Errors and Exceptions – Python.org
- Why you should specify error types in your except – Stackoverflow
Notes
- This is also why we use context managers ↩︎
- This note about finally was lifted straight from the Python docs ↩︎