Python Exceptions – Complete Tutorial

Exceptions refer to the events of errors experienced when executing a program. This Python Exceptions tutorial will overview different exceptions in Python and explain how to catch and mitigate them.

What are the exceptions in Python?

Exceptions are events of failure in your Python code that result from database corruption, broken network connectivity, corrupted data, memory exhaustion, and unsupported user inputs. The purpose of every developer is to catch and mitigate such exceptions to ensure that the final program runs smoothly.

If an exception occurs when an application runs, there is a risk of losing data, corrupting existing data, or even causing the application to crash.

Python allows developers to handle these exceptions using pre-defined exceptions and structured exception handling.

For example, it is always possible to predict the possible error in a Python program and work around it by prompting the user to use the correct input, default value or alternate a program execution flow. 

The difference between syntax errors and exceptions in Python

Syntax errors and exceptions in Python refer to unwanted conditions when executing a program.

Syntax error refers to an error that terminates the execution of a program due to the use of the wrong Python syntax in the code.

The wrong syntax comprises wrong naming styles, incorrect Python keywords, and invalid program structure.

Let’s take a look at the example below to illustrate the syntax error:

age = 18
def check_age(age):
    if age >= 18
        print("Individual is an adult")

If you try to execute the program above, you’ll get the following error message:

  File "syntax_error_example.py", line 4
    if age >= 18
               ^
SyntaxError: invalid syntax

On the other hand, exceptions refer to errors in executing a program, even when a statement is syntactically correct.

If the developer handles exceptions in the code, they do not stop the program’s execution but can lead to a change in the normal flow of the program execution.

As we’ve mentioned, it is possible to handle exceptions in your Python code so they might not be fatal and might not stop Python program execution.

We’ll take a look at how to handle exceptions later, but for now, let’s take a look at what happens if we’re not handling potentially dangerous situations in the code:

x = "test"
for i in range(x):
    print(i)

Execution of the code block above will lead to the following error message and terminate program execution:

Traceback (most recent call last):
  File "exception_example_1.py", line 3, in <module>
    for i in range(x):
TypeError: 'str' object cannot be interpreted as an integer

Python3 Exception Handling

Now, it’s time to look at how to handle exceptions in Python.

Try block syntax in Python

To handle exceptions in Python, you need to use a try-except block, which has the following syntax:

try:
    statement(s)
except [exception_class]:
    statement(s)
else:
    statement(s)
finally:
    statement(s)

Here’s how these clauses work:

  • The try clause runs the block of code where an error is expected to occur.
  • except clause used to define the type of exception expected from the try block of code.
  • If there’s no exception captured, the else statemetns block is executed
  • finally statements are always to be executed regardless of any exceptions caught in the try clause

Catching a single exception in Python

Python allows you to handle any exceptions.

For example, let’s take a look at a program that prompts the user to enter a number, and when the program cannot convert the number to an integer, it raises a ValueError exception:

for i in range(5):
    x = input("Type in a number: ")
    try:
        number = int(x)
    except ValueError:
        print("The value you entered ({}) cannot be converted to an integer".format(x))

Now, let’s try to execute our program:

Type in a number: uytfd
The value you entered (uytfd) cannot be converted to an integer
Type in a number: 5656
Type in a number: 435
Type in a number: dfdf
The value you entered (dfdf) cannot be converted to an integer
Type in a number: shs
The value you entered (shs) cannot be converted to an integer

If the user is typing something wrong, the try block will try to catch an exception by checking the exception classes provided in the except clause.

If an exception doesn’t match any declared exceptions in the except clause, Python will stop program execution with the message unhandled exception.

Keep in mind: when handling exceptions in except blocks, a base class type will catch any raised type of its subclasses. For example, the ArythmeticError exception class will catch OverflowError, FloatingPointError, and ZeroDivisionError exceptions.

Catching multiple exceptions in Python

A try clause can have to have several except clauses to catch different types of exceptions for the same block of code:

for i in range(5):
    try:
        x = int(input("Type in a number: "))
    except (ValueError, TypeError, NameError):
        print("One of the above exceptions was captured during execution")

Here’s an execution output:

Type in a number: 675
Type in a number: jgd
One of the above exceptions wa captured during execution
Type in a number: fg
One of the above exceptions wa captured during execution
Type in a number: 56
Type in a number: 98
for i in range(2):
    user_input = input("Type in a number: ")
    try:
        x = int(user_input)
        y = set()
        y[1] = x
    except ValueError:
        print("The value you entered ({}) cannot be converted to an integer".format(user_input))
    except TypeError:
        print("You can not modify sets!!!")
print("Program execution finished")

Now, let’s try to execute the program above:

Type in a number: asd
The value you entered (asd) cannot be converted to an integer
Type in a number: 1
You can not modify sets!!!
Program execution finished

Even if we provide a valid value, we still have an error in the code, which was caught by the second except clause.

Catching all exceptions in Python

Sometimes we do not know what exception we can get during the code execution, but we need to catch the error and continue the execution flow.

In that case, you can use except clause without providing an exception name, but it is not recommended to hide any issues in the code block.

However, if you have a strong need, it is possible to catch any exception:

import os
path = "/not/existing/path"
try:
    for file in os.listdir(path):
        f = open(os.path.join(path, file))
except ValueError:
    print("Catching ValueError")
except:
    print("Unexpected error happened")
print("Program execution finished")

Here’s the execution output:

Unexpected error happened
Program execution finished

Using the “else” clause in the “try” block

If you’d like to know if the code block has been executed without any errors, you can use else statement after the except clauses:

for i in range(2):
    x = input("Type in a number: ")
    try:
        number = int(x)
    except ValueError:
        print("The value you entered ({}) cannot be converted to an integer".format(x))
    else:
        print("Code block executed without issues")
print("Program execution finished")

Program execution result:

Type in a number: asf
The value you entered (asf) cannot be converted to an integer
Type in a number: 1
Code block executed without issues
Program execution finished

Getting access to the exception class in Python

If you need to get access to the exception class during the exception-handling process, you can do it by using as keyword in the except clause:

try:
    a = 2/0
except Exception as error:
    print(f'Exception info: {error.__doc__}')

Here’s an expected output:

Exception info: Second argument to a division or modulo operation was zero.

The difference between else and finally in Python

Lastly, let’s illustrate how else and finally clauses are working:

for i in [0, 1]:
    try:
        result = 2 / i
    except ZeroDivisionError:
        print("Division by zero")
    else:
        print(f'Division result: {result}')
    finally:
        print("Executing finally clause\n")
print("Program execution finished")

Here’s an execution output:

Division by zero
Executing finally clause
Division result: 2.0
Executing finally clause
Program execution finished

As you can see from the program execution, Python always executes the finally clause statements. At the same time, Python runs the else clause statements only when try block executed without any errors.

Such behavior allows using the finally clause to define clean-up actions such as closing sockets, file descriptors, disconnecting from the database, etc.

Your Python code should have these cleanup actions and execute them at all times, irrespective of the circumstances.

How to catch SyntaxError exception in Python

It is hard to catch the SyntaxError exception, but it is still possible if the SyntaxError is thrown out of an evalexec, or import statements:

try:
    import my_module
except SyntaxError:
    print("Syntax error in my_module")
print("Program execution finished")

Now, if the code in my_module Python module contains syntax errors, program execution will not break:

Syntax error in my_module
Program execution finished

Raising exceptions in Python

You have an opportunity not only to catch and workaround exceptions but also to raise them. So, if you’d like to throw an error in Python, use the raise keyword followed by the error or exception type.

A common example of when you need this is implementing a manager class that allows users to allocate a network from a certain IP pool.

If the manager can’t allocate the network, it makes sense to create a user-defined exception and raise it when the event occurs.

To raise an exception in Python, you need to use raise statement.

The exception you can raise can be any class that derives from an Exception.

A python program implicitly instantiates the constructor by passing an exception class with no arguments, for example:

raise TypeError

As soon as Python runs the statement above, it will produce the following error message:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError

Re-raising exceptions in Python

In some cases, you may need to re-raise an exception that you’re capturing with try-except block.

That’s possible by using a raise statement without providing an exception class:

try:
    raise TypeError(10.578)
except TypeError:
    print('A float number was not captured!')
    raise

The code above will not only raise an exception but also print the reason for that exception:

A float number was not captured!
Traceback (most recent call last):
  File "rerising_exceptions.py", line 2, in <module>
    raise TypeError(10.578)
TypeError: 10.578

Chaining exceptions in Python

Chaining exceptions is useful when you need to transform exceptions.

You can specify an optional from keyword in the raise statement that allows you to re-raise the exception and change its type:

def example():
   try:
     x = input("Type in a number: ")
     number = int(x)
   except ValueError as e:
     raise RuntimeError('User input parsing error occurred') from e
    
example()

Here’s an execution output:

Type in a number: asd
Traceback (most recent call last):
  File "multiple_except.py", line 4, in example
    number = int(x)
ValueError: invalid literal for int() with base 10: 'asd'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "multiple_except.py", line 8, in <module>
    example()
  File "multiple_except.py", line 6, in example
    raise RuntimeError('A parsing error occurred') from e
RuntimeError: A parsing error occurred

Built-In Python exceptions

Most of the built-in exceptions are inheriting the BaseException class. Python interpreter usually generates these exceptions from built-in functions to help developers identify the root cause of an error during Python program execution.

You can inherit built-in exception classes to define new exceptions. All Python developers are encouraged to derive new exceptions from the Exception class or one of its subclasses but not from the BaseException class.

Base exception classes in Python

The tree of base exception classes provides Python developers with lots of standard exceptions. In this part of the article, we’ll review the purpose of Python3 exceptions.

  • BaseException is the base class for all the built-in exceptions. Python developers should not directly inherit this class to define any custom exception classes. This class creates a string representation of the exception by defining the str() method and its arguments. If there are no arguments Python will return an exception with an empty string.
    • GeneratorExit Python throws this exception when a coroutine or generator is closed. It inherits from the BaseException but not the Exception class because it is not an error.
    • KeyboardInterrupt – When a user hits the interrupt key such as Delete or Control+C, this error exception is raised.
    • SystemExit – When the sys.exit() function is called, this exception is raised. In addition, calling this function indicates that clean-up handlers should be executed, that is, the finally clauses of try statements.
    • Exception – is a base class for all built-in non-system-exiting exceptions and user-defined exceptions
Exceptions handling in Python - BaseException
  • ArithmeticError is the base exception class for arithmetic errors such as FloatingPointError, ZeroDivisionError, and OverflowError.
    • FloatingPointError – you can catch this exception when the operation of a floating-point value fails. However, the exception is always defined and it can only be raised if the WANT_SIGFPE_HANDLER symbol is defined in the pyconfig.h file.
    • ZeroDivisionError happens when second argument of the division or modulo operation equals zero
Exceptions handling in Python - ArithmeticError
  • LookupError defines a base class for IndexError and KeyError exceptions that raised by Python when developer is trying to manipulate non-existing or is invalid index or key values at sequence or mapping.
    • IndexError exception happens when a referenced sequence is out of range
    • KeyError you can catch this exception if a mapping key not found in the set of existing keys
    • OverflowError – this exception happens when the results from an arithmetic operation are out of range and also if integers are out of a required range
Exceptions handling in Python - LookupError

Concrete exceptions in Python

Concrete exceptions in Python are build-in exceptions that inherited directly from the Exception class.

Exceptions handling in Python - Exception

Here’s a quick description of all concrete exceptions:

  • BufferError – Python throws this exception when a buffer-related operation fails to execute correctly
  • AssertionError is raised when an assert statement fails in Python code
  • ValueError – you can catch this exception when a built-in function or operation receives the correct type of argument but with an invalid value
    • UnicodeError – This is a subclass of ValueError and is raised when a Unicode decoding or encoding error is captured.
Exceptions handling in Python - ValueError
  • TypeError – Python throws this exception when developer apply an inappropriate type object to a function or an operation. A string that gives the details related to the mismatch is returned by this exception.
  • SystemError – Python throws this exception when the interpreter encounters an internal error.
  • StopIteration – The built-in function next() and the iterator’s __next__() method raise this exception to indicate that all items are produced by the iterator.
  • RuntimeError – raises when no other exception applies. A string is returned with the details of the actual problem encountered during execution.
    • RecursionError – Derived from the RuntimeError and is raised when the maximum recursion depth has been exceeded.
    • NotImplementedError – The exception is derived from the RuntimeError and is raised when derived classes override the abstract methods in user-defined classes.
Exceptions handling in Python - RuntimeError
  • SyntaxError – you can catch this exception when an interpreter encounters wrong syntax. A syntax error can be experienced in an import statement or when reading a standard input or when calling the builtin function eval() or exec().
Exceptions handling in Python - SyntaxError
  • ReferenceError – Python throws this exception when a weak reference proxy to accesses an attribute of the reference after the garbage collection.
  • AttributeError – you’re getting this exception whenever the assignment or reference of an attribute fails. This can happen because the referred attribute does not exist.
  • MemoryError – when Python runs out of memory it throws this exception
  • EOFError – The EOFError is raised if the built-in functions such as input() encounters an end-of-file (EOF) condition without reading any data. For instance, in case the readline() builtin function encounters an EOF, it returns an empty string.
  • ImportError – Raised when the import statement fails to load a module or when the from list in from import has a name that does not exist.
    • ModuleNotFoundError – It is a subclass of the ImportError and is raised when a module could not be found. In addition to that, if None is found in sys.modules the exception can also be raised.
Exceptions handling in Python - ImportError
  • NameError – The exception is raised when a global or local name is not found.
    • UnboundLocalError – This exception is raised when a reference is made to a local variable in a method or a function but there is no value bound to that variable.
Exceptions handling in Python - NameError
  • OSError([arg]) – Whenever a system function returns a system-related error such as I/O failures including “disk full” or “file not found” errors.
    • FileNotFoundError – Raised when a requested file directory or file does not exist.
    • FileExistsError – This exception is raised when attempting to create an already existing file or a directory.
    • TimeoutError – Raised when a system function timed out at the system level.
    • PermissionError – Raised when attempting to run an application without the required access rights.
    • ConnectionError – This is the base class for connection related issues and the subclasses are:
      • BrokenPipeError – Raised when attempting to write on a pipe closed on the other end. Also when attempting to write on a shut down socket.
      • ConnectionResetError – The exception is raised when a peer resets a connection.
      • ConnectionAbortedError – Raised when a peer aborts a connection attempt.
      • ConnectionRefusedError – Raised if a peer refuses a connection attempt.
    • ProcessLookupError – It gets raised in case a specified process does not exist.
    • InterruptedError – The exception is raised when a system class is interrupted by an incoming signal. In Python 3.5+, Python retries system calls whenever a system call is interrupted.
    • IsADirectoryError – It is raised when a file operation such as os.remove() is requested on a directory.
    • NotADirectoryError – Raised when a directory operation such as os.listdir() is requested on an argument that is not a directory.
    • ChildProcessError – Gets raised when a child process operatio fails.
    • BlockingIOError – This error is raised when an operation would block on an object set for non-blocking operation.
Exceptions handling in Python - OSError

Warnings in Python

Developers typically use warning messages when they need to alert the user about some important condition in a program. Usually, such a condition doesn’t raise an exception or terminate the program. A good example might be to print a warning when a program uses an obsolete Python module.

Here’s a list of built-in Python warnings:

  • FutureWarning – This is the base class of warnings about deprecated features.
  • BytesWarning – Base class for warning related to bytearray and bytes.
  • SyntaxWarning – For dubious syntax, this is the base class of such warnings.
  • ImportWarning – Base class for warning related to probable mistakes in module imports.
  • Warning – Base class for warning categories.
  • UserWarning – This is the base class for warnings that have been generated by the user code.
  • ResourceWarning – Base class for warnings associated with resource usage. 
  • PendingDeprecationWarning – This is the base class for warnings related to obsolete features that are expected to be deprecated in the future. For already active deprecation the DeprecationWarning is used.
  • DeprecationWarning – Base class for the warning associated with deprecated features.
  • UnicodeWarning – Base class for warning related to Unicode.
Exceptions handling in Python - Warning

User-defined exceptions in Python

You can inherit from any built-in exception class to create your own exception type.

User-defined exceptions allow us to display more relevant and detailed information when an exception occurs during program execution, for example:

class CustomError(Exception):
    pass
raise CustomError('My custom error')

Here’s the program output:

Traceback (most recent call last):
  File "multiple_except.py", line 4, in <module>
    raise CustomError('My custom error')
__main__.CustomError: My custom error

Customizing exception classes in Python

If you’d like to implement more meaningful exception messages in your Python program, you can do it by implementing an exception class constructor:

ALLOWED_CAR_COLOURS = ['black', 'red', 'green']
class IncorrectCarColourException(Exception):
    """Exception raised for errors in the car colour input.
    Attributes:
        colour -- car colour
    """
    def __init__(self, colour):
        self.colour = colour
        self.message = f'''
            {self.colour} car colour is not allowed.
            Allowed values are {ALLOWED_CAR_COLOURS}
        '''
        super().__init__(self.message)

car_color = input("Enter car colour: ")
if not car_color in ALLOWED_CAR_COLOURS:
    raise IncorrectCarColourException(car_color)

Here’s an execution example:

Enter car colour: white
Traceback (most recent call last):
  File "multiple_except.py", line 22, in <module>
    raise IncorrectCarColourException(car_color)
__main__.IncorrectCarColourException:
            white car colour is not allowed.
            Allowed values are ['black', 'red', 'green']

Summary

We have seen different types of exceptions, both built-in and user-defined ones. In addition to that, we have reviewed specific exceptions and errors that occur to raise these exceptions. The article has a detailed explanation of how to handle and raise exceptions in a Python program.