try ... except ... finally Statement

- Handle selected exceptions.
- E.g. ask user for input until a valid integer has been entered:
while True: try: num1 = int(input("Please enter a number: ")) break except ValueError: print("Oops! That was no valid integer. Try again...") - We can access arguments of any exception by bounding the exception to a variable:
except Exception as e: print(e.args)Though usually you do not need to do this since if it is just for the sake of printing in python all of the built-in exceptions will log the
argstoo (more on that when we talked about classes).Just if you are really curious
In the built-in exception classes we have a method called
__str__()which will be called by Python when it tries to convert a class object to a string. So inside that class Python is returning theargsas a string. - Or we can handle different cases:
try: # ... result = 1 / 0 except ZeroDivisionError as e: print(e) except Exception as e: # Catches any kind of error print(e) finally: # Cleanup codeKeep in mind that at most one of the
exceptblocks will be executed. - You can have multiple
try ... exceptblocks inside one another:def divide_numbers(numerator: int, denominator: int) -> float | None: try: return numerator / denominator except ValueError: print("Something wrong with values") return None # Note that we've caught the exception here, thus if python enters this block of code it will returns None. def get_int_number() -> int: while True: try: return int(input("Please enter a number: ")) except ValueError: print("Oops! That was no valid integer. Try again...") def calc() -> None: numerator = get_int_number() nominator = get_int_number() try: result = divide_numbers(numerator, nominator) print(result) except Exception as e: print("Calc function's except block:") print(e) calc()Note that though it might be hard to see at first but what is really happening here is something like this:
try: try: # ... except ValueError: # ... except Exception as e: # ... exceptblocks cannot catch each other’s errors. if you wanna do that you need to do it manually:try: pass except NameError as name_error: try: pass except Exception as any_error: pass- You can also handle multiple exception in one
exceptblock:except (RuntimeError, TypeError, NameError): pass
try Clause.
- Inside the
tryblock we write the code that might encounter some exceptions. - Python runs this block of code first, or let’s say tries to execute it.
- Possible scenarios:
- Nothing goes south Python runs all the codes in this block and jumps to
finallyblock if present. - An error, oh, now Python checks if we have defined an
exceptblock that matches the name of current exception. If it finds one it executes its block and then continues with thefinallyblock. - An error without a handler, thus Python tries to pass down the error to the outer
try ... exceptblock. If there is none, or we ain’t handling it there either then it’s an unhandled exception. This causes our Python program to crash and terminate with that error.
- Nothing goes south Python runs all the codes in this block and jumps to
except Clause.

- Breaking out of the normal flow of control of a code block.
- Handle errors or other exceptional conditions.
- An exception is raised.
- Python interpreter raises an exception when it detects a run-time error.
- Explicitly raise an exception with the
raisestatement. - Cannot repair the cause of the error and retry the failing operation. But we can re-execute the function again if the exception was raised in a function for example.
- If the exception is not handled at all, the interpreter terminates execution of the program.
finally Clause.
- Optional.
- Cleanup code which does not handle the exception.
- In Real world apps it is useful for releasing external resources (such as files or network connections).
- Executed whether:
- An exception occurred or not in the preceding code.
- You have used a
return,continue, orbreakstatement inside yourtryclause. - You’ve handled the exception gracefully or not.
- You have a except clause for the raised exception or not. In this case it will executes the codes inside
finallyand then reraise the exception. - An exception was raised in your
except/elseclause or not. In the case of exception it will be reraised after it finishes executing thefinallyblock.
- Here we can skip reraising errors by using a
return,continue, orbreakstatement.
[!CAUTION]
The
returnstatement inside thefinallytakes precedence over thereturnstatement inside thetryclause!def func(): try: return 1 finally: return 2 print(func()) # 2
[!TIP]
There are some objects with a defined standard clean-up actions after object is no longer needed, regardless of whether or not the operation using the object succeeded or failed. E.g.
withstatement is one of them.
else Clause
- Optional.
- Defined after all
exceptclauses. - Code that must be executed if the
tryclause does notraisean exception. -
try: print("some code...") except Exception: print("...") else: print("won't be executed if any exception was occurred inside the try clause")
raise
- Allows the programmer to force a specified exception to occur.
raise Exception("Bad user input") - It takes an optional argument.
- This argument indicates the exception to be raised.
- Should be an exception instance or an exception class. E.g.
BaseException, orException, or classes who derive from them. - BTW we can delegate the task of instantiating an exception instance to the Python interpreter:
raise ValueError # Same as raise ValueError()But here we cannot pass any value to its
constructor. - You can pass a string to it as its argument:
raise "SomeError"
- If no expressions are present,
raisere-raises the exception that is currently being handled (AKA active exception). This is useful when you just wanna log something somewhere that this exception occurred:try: result = 1 / 0 except ZeroDivisionError as e: raise - Called on its own with not active exception or an exception in front of it? a
RuntimeErrorexception will be raised.raise
Exception Chaining
try:
open("somefile.txt")
except OSError:
raise RuntimeError("unable to handle error")
- Here we will see the
Tracebackof both exceptions raised. One which isFileNotFoundError, raised by OS. And theRuntimeErrorwhich we’ve raised. - Though you can also specify that an exception was a direct result of another exception with
fromclause. This is useful when we are handling multiple exception and are transforming them:except (OSError, ConnectionError) as e: raise RuntimeError from e - To disable this chaining behavior we can say:
except OSError: raise RuntimeError from None
Dealing with Multiple Exceptions – ExceptionGroup
- Instead of raising the first exception we can collect all of them.
-
Useful in concurrency and parallelism.


- Also good for where it is desirable to continue execution and collect multiple errors.
def f():
excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)
try:
f()
except* OSError as e:
print("There were OSErrors")
except* SystemError as e:
print("There were SystemErrors")
Note how we discerned the exception inside a group with except*. Though this is a contrived example but I guess this can give you a glimpse of what it would look like in a more realistic code:
excs = []
for test in tests:
try:
test.run()
except Exception as e:
excs.append(e)
if excs:
raise ExceptionGroup("Test Failures", excs)
Here for example we are running some tests and wanted to catch all the exceptions after we’ve executed all the tests and raise them all at once.