In this article, we’ll look at how to handle errors and exceptions in Python — a concept known as “exception handling”.

There are usually two types of errors you’ll experience while programming in Python: syntax errors, and exceptions.

Any error resulting from invalid syntax, indentation or programming structure is often regarded as a syntax error. When a syntax error occurs, the program crashes at the point the syntax error occurred.

An exception is an anomaly that disrupts the normal flow of a computer program. When exceptions occur, we’re expected to handle these exceptions to ensure that our programs don’t crash abruptly.

Exception handling is the process whereby checks are implemented in computer programs to handle errors — whether expected or not — that may occur during the execution of our programs. (Python tends to have more of a “do the thing and ask for forgiveness” style of programming than most other languages, as discussed here and here.)

Python Exception Handling

Python, like every other programming language, has a way of handling exceptions that occur during a program’s execution. This means that exceptions are handled gracefully: our Python program doesn’t crash. When an error occurs at runtime in a program that’s syntactically correct, Python uses the try statement and except clause to catch and handle exceptions.

Since most of the exceptions are expected, it’s necessary to be more targeted or specific with exception handling in our Python programs. Specific exception handling makes debugging of programs easier.

Some Standard Python Exceptions

Python has a list of built-in exceptions used to handle different exceptions. Below are some built-in Python Exceptions.

SNException NameDescription
1ExceptionAll user-defined exceptions should also be derived from this class.
2ArithmeticErrorThe base class for those built-in exceptions that are raised for various arithmetic errors.
3BufferErrorRaised when a buffer related operation cannot be performed.
4LookupErrorThe base class for the exceptions that are raised when a key or index that’s used on a mapping or sequence is invalid.
5AssertionErrorRaised when an assert statement fails.
6AttributeErrorRaised when an attribute reference or assignment fails.
7ImportErrorRaised when the import statement has troubles trying to load a module.
8IndexErrorRaised when a sequence subscript is out of range.
9KeyErrorRaised when a mapping (dictionary) key is not found in the set of existing keys.
10NameErrorRaised when a local or global name is not found.
11OverflowErrorRaised when the result of an arithmetic operation is too large to be represented.
12RuntimeErrorRaised when an error is detected that doesn’t fall in any of the other categories.
13StopIterationRaised by built-in function next() and an iterator’s __next__() method to signal that there are no further items produced by the iterator.
14SyntaxErrorRaised when the parser encounters a syntax error.
15TypeErrorRaised when an operation or function is applied to an object of inappropriate type.
16ValueErrorRaised when an operation or function receives an argument that has the right type but an inappropriate value.
17ZeroDivisionErrorRaised when the second argument of a division or modulo operation is zero.
18FileExistsErrorRaised when trying to create a file or directory which already exists.
19FileNotFoundErrorRaised when a file or directory is requested but doesn’t exist.

Handling Python Exceptions with the try and except Statements

The try and except blocks are used for exception handling in Python. The syntax can look like this:

try: except: 

The try block contains a section of code that can raise an exception, while the except block houses some code that handles the exception.

Let’s take a simple example below:

print(3/0)

The code above will generate an error message while the program terminates:

Traceback (most recent call last): File "/home/ini/Dev/Tutorial/sitepoint/exception.py", line 53, in <module> print(3/0)
ZeroDivisionError: division by zero

The line of code that throws the exception can be handled as follows:

try: print(3/0)
except ZeroDivisionError: print("Cannot divide number by Zero")

In the example above, we place the first print statement within the try block. The piece of code within this block will raise an exception, because dividing a number by zero has no meaning. The except block will catch the exception raised in the try block. The try and except blocks are often used together for handling exceptions in Python. Instead of the previous error message that was generated, we simply have “Cannot divide number by Zero” printed in the console.

Multiple Python Excepts

There are cases where two or more except blocks are used to catch different exceptions in Python. Multiple except blocks help us catch specific exceptions and handle them differently in our program:

try: number = 'one' print(number + 1) print(block)
except NameError: print("Name is undefined here")
except TypeError: print("Can't concatenate string and int")

Here’s the output of the code above:

Can't concatenate string and int

From the example above, we have two except blocks specifying the types of exceptions we want to handle: NameError and TypeError. The first print statement in the try block throws a TypeError exception. The Python interpreter checks through each except clause to find the appropriate exception class, which is handled by the second except block. “Can’t concatenate string and int” is printed in the console.

The second print statement in the try block is skipped because an exception has occurred. However, any code after the last except clause will be executed:

try: number = 'one' print(number + 1) print(block)
except NameError: print("Name is undefined here")
except TypeError: print("Can't concatenate string and int") for name in ['Chris', 'Kwame', 'Adwoa', 'Bolaji']: print(name, end=" ")

Here’s the output of the code above:

Can't concatenate string and int
Chris Kwame Adwoa Bolaji

The for loop after the try and except blocks is executed because the exception has been handled.

A generic Python except

We can have a generic except block to catch all exceptions in Python. The generic except block can be used alongside other specific except blocks in our program to catch unhandled exceptions. It’s logical to place the most generic except clause after all specific except blocks. This will kick in when an unhandled exception occurs. Let’s revise our previous example with a general except block coming in last:

names = ['Chris', 'Kwame', 'Adwoa', 'Bolaji']
try: print(names[6]) number = 'one' print(number + 1) print(block)
except NameError: print("Name is undefined here")
except TypeError: print("Can't concatenate string and int")
except: print('Sorry an error occured somewhere!') for name in names: print(name, end=" ")

Here’s the output of the code above:

Sorry an error occured somewhere!
Chris Kwame Adwoa Bolaji

An IndexError exception occurs, however, since it isn’t handled in any of the specified except blocks. The generic except block handles the exception. The statement in the generic block is executed and the for loop after it is also executed, with the respective output printed in the console.

The raise statement

Sometimes in our Python programs we might want to raise exceptions in certain conditions that don’t match our requirement using the raise keyword. The raise statement consists of the keyword itself, an exception instance, and an optional argument. Let’s take a code snippet below:

def validate_password(password): if len(password) < 8: raise ValueError("Password characters less than 8") return password try: user_password = input('Enter a password: ') validate_password(user_password)
except ValueError: print('Password should have more characters')

The raise ValueError checks if the password meets the required length and raises the specified exception if the condition isn’t met. Stack OverFlow examines why it’s OK to raise exceptions instead of just printing to the console:

Raising an error halts the entire program at that point (unless the exception is caught), whereas printing the message just writes something to stdout — the output might be piped to another tool, or someone might not be running your application from the command line, and the print output may never be seen.

Raise an exception, to delegate the handling of that condition to something further up the callstack.

The else clause

The else clause can be added to the standard try and except blocks. It’s placed after the except clause. The else clause contains code that we want executed if the try statement doesn’t raise an exception. Let’s consider the following code:

try: number = int(input('Enter a number: ')) if number % 2 != 0: raise ValueError
except ValueError: print("Number must be even")
else: square = number ** 2 print(square)

When a user enters an even number, our code runs without raising exceptions. Then the else clause executes. We now have the square of our even number printed in the console. However, exceptions that may occur in the else clause aren’t handled by the previous except block(s).

The finally clause

The finally clause can be added to try and except blocks and should be used where necessary. Code in the finally clause is always executed whether an exception occurs or not. See the code snippet below:

try: with open('robots.txt', 'r', encoding='UTF-8') as f: first_line = f.readline()
except IOError: print('File not found!')
else: upper_case = first_line.upper() print(upper_case.index('x'))
finally: print('The Python program ends here!')

Here’s the output of the code above:

The Python program ends here!
Traceback (most recent call last): File "/home/ini/Dev/python/python_projects/extra.py", line 89, in <module> print(upper_case.index('x'))
ValueError: substring not found

In the example above, we’re attempting to read a robots.txt file in the try clause. Since there’s no exception raised, the code in the else clause is executed. An exception is raised in the else clause because the substring x wasn’t found in the variable upper_case. When there’s no except clause to handle an exception — as seen the code snippet above — the finally clause is executed first and the exception is re-raised after.

The Python documentation explains it like so:

An exception could occur during execution of an except or else clause. Again, the exception is re-raised after the finally clause has been executed.

Exception Groups

ExceptionGroup became available in Python 3.11. It provides a means of raising multiple unrelated exceptions. The preferred syntax for handling ExceptionGroup is except*. The syntax for exception groups looks like this:

ExceptionGroup(msg, excs)

When initialized, exception groups take two arguments, msg and excs:

  • msg: a descriptive message
  • excs: a sequence of exception subgroups

Let’s create an instance of ExceptionGroup:

eg = ExceptionGroup('group one', [NameError("name not defined"), TypeError("type mismatch")])

When instantiating an exception group, the list of exception subgroups can’t be empty. We’ll raise an instance of the exception group we created earlier:

raise eg

Here’s the output of the code above:

+ Exception Group Traceback (most recent call last):
| File "<string>", line 10, in <module> | ExceptionGroup: group one (2 sub-exceptions) +-+---------------- 1 ---------------- | NameError: name not defined +---------------- 2 ---------------- | TypeError: type mismatch +------------------------------------

The displayed traceback shows all exception subgroups contained in the exception group.

As stated earlier, ExceptionGroup is better handled with the except* clause, because it can pick out each specific exception in the exception group. The generic except clause will only handle the exception group as a unit without being specific.

See the code snippet below:

try: raise ExceptionGroup('exception group', [NameError("name not defined"), TypeError("type mismatch"), ValueError("invalid input")])
except* NameError as e: print("NameError handled here.")
except* TypeError as e: print("TypeError handled here.")
except* ValueError: print("ValueError handled here.")

Here’s the output of that code:

NameError handled here.
TypeError handled here.
ValueError handled here.

Each except* clause handles a targeted exception subgroup in the exception group. Any subgroup that’s not handled will re-raise an exception.

User-defined Exceptions in Python

Built-in exceptions are great, but there may be a need for custom exceptions for our software project. Python allows us to create user-defined exceptions to suit our needs. The Python Documentation states:

All exceptions must be instances of a class that derives from BaseException.

Custom exceptions are derived by inheriting Python’s Exception class. The syntax for a custom exception looks like this:

class CustomExceptionName(Exception): pass
try: pass
except CustomExceptionName: pass

Let’s create a custom exception and use it in our code in the following example:

class GreaterThanTenError(Exception): pass try: number = int(input("Enter a number: ")) if number > 10: raise GreaterThanTenError
except GreaterThanTenError: print("Input greater than 10")
else: for i in range(number): print(i ** 2, end=" ")
finally: print() print("The Python program ends here")

In the example above, we create our own class with an exception name called GreaterThanTenException, which inherits from the Exception superclass. We place in it some code that may raise an exception in the try block. The except block is our exception handler. The else clause has code to be executed if no exception is thrown. And lastly, the finally clause executes no matter the outcome.

If a user of our Python program inputs a number above 10, a GreaterThanTenError will be raised. The except clause will handle exceptions, and then the print statement in the finally clause is executed.

Conclusion

In this tutorial, we’ve learned the main difference between syntax errors and exceptions. We’ve also seen that a syntax error or exception disrupts the normal flow of our program.

We’ve also learned that the try and except statements are the standard syntax for handling exceptions in Python.

Exception handling is important when building real-world applications, because you want to detect errors and handle them appropriately. Python provides a long list of in-built exceptions that prove useful when handling exceptions.

Similar Posts