- Exception handling in Python (try, except, else, finally)
- Basic exception handling in Python: try . except .
- Catch multiple exceptions
- Apply different operations to multiple exceptions
- Apply the same operation to multiple exceptions
- Catch all exceptions
- Wildcard except (Bare except)
- Base class: Exception
- Execute action if no exception occurs: try . except . else .
- Clean-up action: try . except . finally .
- Ignore exceptions: pass
- Practical example: Reading image files
Exception handling in Python (try, except, else, finally)
In Python, try and except are used to handle exceptions, which are errors detected during execution. With try and except , even if an exception occurs, the process continues without terminating. Additionally, else and finally can be used to set the ending process.
Basic exception handling in Python: try . except .
For example, when attempting division by zero, a ZeroDivisionError is raised, causing the process to end.
# print(1 / 0) # ZeroDivisionError: division by zero
To catch this exception, write the code as follows:
try: print(1 / 0) except ZeroDivisionError: print('Error') # Error
By using except as : , the exception object is stored in the variable. You can choose any name for the variable, but names like e and err are commonly used.
The exception object contains error messages that are displayed when an exception occurs, allowing you to check the error details by outputting it.
try: print(1 / 0) except ZeroDivisionError as e: print(e) print(type(e)) # division by zero #
In Python 2, it should be written as except , : .
You can also specify a base class. For example, ArithmeticError is the base class for ZeroDivisionError . The variable stores the exception object of the derived class that actually occurred.
print(issubclass(ZeroDivisionError, ArithmeticError)) # True try: print(1 / 0) except ArithmeticError as e: print(e) print(type(e)) # division by zero #
Check the official documentation for built-in exceptions in Python.
When an exception occurs in the try clause, the subsequent process in the try clause is skipped.
As shown in the example below, if an exception occurs in the middle of the for loop, the loop ends at that point, and the process in the except clause is executed.
try: for i in [-2, -1, 0, 1, 2]: print(1 / i) except ZeroDivisionError as e: print(e) # -0.5 # -1.0 # division by zero
You can specify the process to execute after the except clause using the else and finally clauses, which will be described later.
Catch multiple exceptions
Define the following function that catches ZeroDivisionError .
def divide(a, b): try: print(a / b) except ZeroDivisionError as e: print('catch ZeroDivisionError:', e)
This function can catch ZeroDivisionError , but it cannot catch other exceptions.
divide(1, 0) # catch ZeroDivisionError: division by zero # divide('a', 'b') # TypeError: unsupported operand type(s) for /: 'str' and 'str'
Apply different operations to multiple exceptions
You can specify multiple except clauses and assign different operations for each exception.
def divide_each(a, b): try: print(a / b) except ZeroDivisionError as e: print('catch ZeroDivisionError:', e) except TypeError as e: print('catch TypeError:', e) divide_each(1, 0) # catch ZeroDivisionError: division by zero divide_each('a', 'b') # catch TypeError: unsupported operand type(s) for /: 'str' and 'str'
Apply the same operation to multiple exceptions
You can specify multiple exception names in a single except clause by using a tuple .
def divide_same(a, b): try: print(a / b) except (ZeroDivisionError, TypeError) as e: print(e) divide_same(1, 0) # division by zero divide_same('a', 'b') # unsupported operand type(s) for /: 'str' and 'str'
Catch all exceptions
It is also possible to catch all exceptions without specifying them.
Wildcard except (Bare except)
All exceptions can be caught by omitting the exception name from the except clause. If there are multiple except clauses, the exception name can be omitted only in the last except clause.
The except clause without exception names is called wildcard except, bare except, etc. Use it with caution, as mentioned in the official documentation.
The last except clause may omit the exception name(s), to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! 8. Errors and Exceptions — Handling Exceptions — Python 3.9.0 documentation
def divide_wildcard(a, b): try: print(a / b) except: print('Error') divide_wildcard(1, 0) # Error divide_wildcard('a', 'b') # Error
Using a wildcard except, all exceptions, including SystemExit (raised by sys.exit() , etc.) and KeyboardInterrupt (raised when the interrupt key Ctrl + C is pressed), are caught.
In many cases, it is preferable not to catch these exceptions, so using Exception , as described next, is a better option.
Base class: Exception
You can specify Exception in the except clause, which is the base class for all built-in, non-system-exiting exceptions.
def divide_exception(a, b): try: print(a / b) except Exception as e: print(e) divide_exception(1, 0) # division by zero divide_exception('a', 'b') # unsupported operand type(s) for /: 'str' and 'str'
The class hierarchy for built-in exceptions is as follows.
BaseException ├── BaseExceptionGroup ├── GeneratorExit ├── KeyboardInterrupt ├── SystemExit └── Exception ├── StopAsyncIteration ├── StopIteration ├── . .
Since SystemExit and KeyboardInterrupt do not inherit Exception , specifying Exception in the except clause will not catch sys.exit() and the exception of interrupt key input.
This exception is raised by the sys.exit() function. It inherits from BaseException instead of Exception so that it is not accidentally caught by code that catches Exception. This allows the exception to properly propagate up and cause the interpreter to exit. Built-in Exceptions — SystemExit — Python 3.11.3 documentation
Raised when the user hits the interrupt key (normally Control-C or Delete). During execution, a check for interrupts is made regularly. The exception inherits from BaseException so as to not be accidentally caught by code that catches Exception and thus prevent the interpreter from exiting. Built-in Exceptions — KeyboardInterrupt — Python 3.11.3 documentation
The base class for all built-in exceptions, including SystemExit and KeyboardInterrupt , is BaseException . If you specify BaseException instead of Exception in the except clause, all exceptions will be caught, as well as wildcard except.
It is better to specify the expected exceptions as much as possible in the except clause because catching even an unexpected exception may cause a bug.
Execute action if no exception occurs: try . except . else .
You can specify the action to be executed if there is no exception in the else clause. If an exception occurs and is caught by except , the action in the else clause is not executed.
def divide_else(a, b): try: print(a / b) except ZeroDivisionError as e: print('catch ZeroDivisionError:', e) else: print('finish (no error)') divide_else(1, 2) # 0.5 # finish (no error) divide_else(1, 0) # catch ZeroDivisionError: division by zero
Clean-up action: try . except . finally .
In the finally clause, you can specify the clean-up action to be executed whether an exception occurs or not.
def divide_finally(a, b): try: print(a / b) except ZeroDivisionError as e: print('catch ZeroDivisionError:', e) finally: print('all finish') divide_finally(1, 2) # 0.5 # all finish divide_finally(1, 0) # catch ZeroDivisionError: division by zero # all finish
You can also use the else and finally clause together. If no exception occurs, the else clause is executed and then the finally clause is executed.
def divide_else_finally(a, b): try: print(a / b) except ZeroDivisionError as e: print('catch ZeroDivisionError:', e) else: print('finish (no error)') finally: print('all finish') divide_else_finally(1, 2) # 0.5 # finish (no error) # all finish divide_else_finally(1, 0) # catch ZeroDivisionError: division by zero # all finish
Ignore exceptions: pass
If you want to catch an exception and continue without taking any action, use pass .
def divide_pass(a, b): try: print(a / b) except ZeroDivisionError: pass divide_pass(1, 0)
See the following article for details on the pass statement.
Practical example: Reading image files
A convenient example of using exception handling is reading image files.
The following is an example of resizing the image files in the folder using Pillow.
Without exception handling:
Get all file paths in the folder with glob() and resize only files that match specific extensions.
import os import glob from PIL import Image dst_dir = 'data/temp/images_half' os.makedirs(dst_dir, exist_ok=True)
files = glob.glob('./data/temp/images/*') for f in files: root, ext = os.path.splitext(f) if ext in ['.jpg', '.png']: img = Image.open(f) img_resize = img.resize((img.width // 2, img.height // 2)) basename = os.path.basename(root) img_resize.save(os.path.join(dst_dir, basename + '_half' + ext))
Since image files can have various extensions, it is difficult to specify all of them.
files = glob.glob('./data/temp/images/*') for f in files: try: img = Image.open(f) img_resize = img.resize((img.width // 2, img.height // 2)) root, ext = os.path.splitext(f) basename = os.path.basename(root) img_resize.save(os.path.join(dst_dir, basename + '_half' + ext)) except OSError as e: pass
All files that can be opened with Pillow’s Image.open() are resized.
The approach that explicitly checks the conditions, like in the first example, is called «LBYL: Look Before You Leap», while the approach that uses exception handling, like in the second example, is called «EAFP: Easier to Ask for Forgiveness than Permission».
Both approaches have pros and cons, but using exception handling can make the code more concise when dealing with processes that involve many conditions.