Exception Handling With the Python try-except Statements

Introduction

Exception handling is a mechanism to manage errors that arise during program execution, commonly referred to as runtime errors. Such errors, for example, can be file I/O operations, network requests, or mathematical calculations. In Python, tryexcept statements are employed to specify where you anticipate the errors to occur and how to handle them.

The exception itself is a type of Python object that is “raised” to alert different parts of the program that an error occurred. Exception can either be raised by the Python interpreter behind the scenes, or by your own code using the raise keyword.

Basics of tryexcept Statement Syntax

Let’s ease into exception handling concepts by looking at the very basic tryexcept statement syntax. This syntax consists of the try keyword followed by a colon : with a code block underneath. After that, the except keyword specifies the type of exception to handle, and is again followed by a colon : and another code block.

try:
    try-block
except ExceptionType:
    except-block
  • try: The keyword that starts the statement.
  • try-block: The block of code where you anticipate exceptions. If an exception occurs within this block, Python looks for an appropriate except block to handle it.
  • except: Defines the exception handling. If an exception of type ExceptionType or any of its subclasses occurs within the try block, no further code is executed in try-block and the code within except-block is run next.
  • except-block: The code that handles the exception.

The process of intercepting an exception for custom handling is also referred to as “catching” an exception.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    
    fraction = numerator/denominator
    print(f"The decimal fraction is {fraction}.") 
except ZeroDivisionError:
    print("The fraction is invalid because the denominator is zero.")

Output 1:

Enter the numerator> 2
Enter the denominator> 5
The decimal fraction is 0.4.

Output 2:

Enter the numerator>? 2
Enter the denominator>? 0
The fraction is invalid because the denominator is zero.

The example above asks the user to enter a numerator and a denominator and then prints a decimal fraction by dividing them. Line 1 starts the tryexcept statement, with the try block indented underneath. The except on line 7 handles any ZeroDivisionError exceptions that happen in the try block.

The first sample run shows the program running normally with no exception. The user inputs 2 and 5, and the fraction 0.4 is printed by line 6 of the code.

The second sample output demonstrates the ZeroDivisionError exception being handled. Here, the user enters 2 and 0, causing the division on line 5 to fail and raise a ZeroDivisionError exception. When this happens, no further code is executed in the try block (the print is skipped) and the code goes to line 8 and prints the appropriate message.

Now, with the same example program, let’s see what happens when an exception that is not ZeroDivisionError happens. We’ll keep the code the same, but we’ll enter a string that cannot be converted into a number.

Output:

Enter the numerator> Hello

Traceback (most recent call last):
  File "Contents/plugins/python-ce/helpers/pydev/pydevconsole.py", line 364, in runcode
    coro = func()
  File "<input>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'Hello'

This second run of the program receives the string “Hello” at the numerator prompt. Obviously, it cannot be converted into a number, and therefore, line 2 fails with a ValueError exception. But this type of exception is not specified by the except, and therefore, its block does not handle it, and the nice message is not printed. Instead, the exception is propagated to the calling code, which in this case is the terminal. The terminal handles it by printing the exception information, starting on line 3.

Handling Multiple Exceptions

The previous example handled a ZeroDivisionError exception but let a ValueError go through. Let’s see how we can handle a ValueError as well.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    
    fraction = numerator/denominator
    print(f"The decimal fraction is {fraction}.") 
except (ZeroDivisionError, ValueError):
    print("The value you entered is invalid.")

Output:

Enter the numerator> 2
Enter the denominator> 0
The value you entered is invalid.

Output:

Enter the numerator>? Hello again
The value you entered is invalid.

This time, the example above handles both a ZeroDivisionError and a ValueError. It accomplishes this by listing all the exceptions to handle within a comma-separated list, enclosed by parentheses () on line 8. The block underneath the except will be executed when any one of the listed exceptions occurs.

The previous example handled both the ZeroDivisionError and ValueError exceptions with the same code block, which printed the same generic message. However, these are generated by two different types of user input problems, and that’s why there is a way to handle each exception separately. The next example will do exactly that to let the user know what went wrong more specifically.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    
    fraction = numerator/denominator
    print(f"The decimal fraction is {fraction}.") 
except ZeroDivisionError:
     print("The fraction is invalid because the denominator is zero.")
except ValueError:
     print("The input you entered is invalid because it can't be converted to a number.")

Output 1:

Enter the numerator> 2
Enter the denominator> 0
The fraction is invalid because the denominator is zero.

Output 2:

Enter the numerator>? Hello again
The input you entered is invalid because it can't be converted to a number.

In the example above, each of the errors is handled differently by two separate except clauses. The except on line 7 handles the ZeroDivisionError, and the one on line 9, ValueError. Each prints a different message when either the denominator is zero, or the input can’t be converted to a number, respectively.

The general syntax for the tryexcept for handling multiple exceptions in the try block is as follows:

try:
    try-block
except ExceptionType1:
    except-block1
except (ExceptionType2, ExceptionType3):
    except-block2

Handling All Exceptions

If you omit the exception type in the except clause, you can catch and handle all exceptions.

The basic syntax for this is as follows:

try:
    try-block
except:
     except-block

While this can be useful for generic error handling, it’s generally good practice to catch specific exceptions whenever possible. A catch-all exception handler will not only handle anticipated exceptions, but also hide unanticipated exceptions you may want to be aware of and handle differently.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    fraction = numerator / denominator
    print(f"The decimal fraction is {fraction}.")
except:
    print("Something bad happened.")

Output:

Enter the numerator> 2
Enter the denominator> 0
Something bad happened.

The example above catches all exceptions on line 6 and handles them in the block below on line 7.

Another way to catch all exceptions is to catch the Exception type. Doing this will catch all because Exception is the base class of all Python exceptions, and the except clause catches all the exceptions it’s given and their descendant types.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    fraction = numerator / denominator
    print(f"The decimal fraction is {fraction}.")
except Exception:
    print("Something bad happened again.")

Output:

Enter the numerator> 2
Enter the denominator> 0
Something bad happened again.

This example is very similar to the previous one, however on line 6, the except clause is given the Exception type, which is another way of catching all exceptions.

Accessing Exception Information

As objects, Python exceptions have some properties that can be accessed within the except block. To do so, you use the as keyword followed by a variable name after the exception type in the except clause. The exception object will then become accessible in the except block through a variable of that name.

Here is the basic syntax for using as:

try:
    try-block
except ExceptionType as e:
    except-block

Inside except-block, the properties of the exception e can be accessed like they can for other Python objects.

Example:

try:
    x = 1/0
except ZeroDivisionError as e:
    print(f"An exception of type {type(e).__name__} occurred: {e}")

Output:

An exception of type ZeroDivisionError occurred: division by zero

In the example above, the exception variable e is designated on line 3. It is assigned the exception object that occurs due to the division by zero on line 2. Inside the except block, line 4 prints both the exception’s class name, i.e. ZeroDivisionError, and a description of the error. The class name is obtained as a string by accessing the class, using the type() function, and then its __name__ property. The exception’s description, i.e. “division by zero”, is obtained directly from the exception object e. Since e is used within an f-string, Python implicitly converts it to a string using its __str__() method. Writing e.__str__() rather than just e would achieve the same result here, but is unnecessary since Python does it automatically.

You can use as with except clauses that catch multiple exception types precisely the same way. The exception variable will assume whichever exception happens to occur.

Example:

try:
    numerator = int(input("Enter the numerator"))
    denominator = int(input("Enter the denominator"))
    
    fraction = numerator/denominator
    print(f"The decimal fraction is {fraction}.") 
except (ZeroDivisionError, ValueError) as e:
     print(f"An exception of type {type(e).__name__} occurred: {e.__str__()}")

Output 1:

Enter the numerator> I'm not a number
An exception of type ValueError occurred: invalid literal for int() with base 10: "I'm not a number"

Output 2:

Enter the numerator> 2
Enter the denominator> 0
An exception of type ZeroDivisionError occurred: division by zero

The example above is again a variation of the now-very-familiar fraction program. The first sample run generates a ValueError exception, and the second run a ZeroDivisionError. The except block prints the exception information on line 8, each time for the exception that occurred. You can see from the first output that the exception description is tailored to the individual error occurrence. The description includes the specific value it received for its erroneous input, “I’m not a number”.

Summary & Reference for the Python tryexcept Statements

Exception handling is a mechanism used to manage runtime errors that arise during program execution. In Python, tryexcept statements are used to specify where you anticipate the errors to occur and how to handle them.


The exception itself is a type of Python object that is “raised” to alert different parts of the program that an error occurred.


Python’s tryexcept statements are employed to specify the code block in which you anticipate exceptions to occur, and the code block with which you handle them.

try:
    try-block  # Exception can occur here
except ExceptionType: 
    except-block  # Handles exception of type `ExceptionType` or its descendant exception objects

Multiple exceptions can be handled using a single except block by listing the exception types within parentheses and separating them with commas. Additionally, separate except blocks can be used to handle different types of exceptions individually, allowing for customized exception handling logic for each exception.

try:
    try-block
except ExceptionType1:
    except-block1  # Handles ExceptionType1
except (ExceptionType2, ExceptionType3):
    except-block2  # Handles ExceptionType2 and ExceptionType3

All exceptions can be caught and handled uniformly with a catch-all except that has no specified exception type. It’s generally recommended to specify exception types whenever possible to maintain code clarity and avoid masking unexpected errors.

try:
    try-block
except:
     except-block

Another way to catch all exceptions is to catch Exception type, which is the base class for Python exceptions.

try:
    try-block
except Exception:
     except-block

You can use the as keyword within the except clause to access detailed information about the raised exception, including its type and any associated error messages or data.

try:
    x = 1/0
except ZeroDivisionError as e:
    print(f"An exception of type {type(e).__name__} occurred: {e}")