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.
SN | Exception Name | Description |
---|---|---|
1 | Exception |
All user-defined exceptions should also be derived from this class. |
2 | ArithmeticError |
The base class for those built-in exceptions that are raised for various arithmetic errors. |
3 | BufferError |
Raised when a buffer related operation cannot be performed. |
4 | LookupError |
The base class for the exceptions that are raised when a key or index that’s used on a mapping or sequence is invalid. |
5 | AssertionError |
Raised when an assert statement fails. |
6 | AttributeError |
Raised when an attribute reference or assignment fails. |
7 | ImportError |
Raised when the import statement has troubles trying to load a module. |
8 | IndexError |
Raised when a sequence subscript is out of range. |
9 | KeyError |
Raised when a mapping (dictionary) key is not found in the set of existing keys. |
10 | NameError |
Raised when a local or global name is not found. |
11 | OverflowError |
Raised when the result of an arithmetic operation is too large to be represented. |
12 | RuntimeError |
Raised when an error is detected that doesn’t fall in any of the other categories. |
13 | StopIteration |
Raised by built-in function next() and an iterator’s __next__() method to signal that there are no further items produced by the iterator. |
14 | SyntaxError |
Raised when the parser encounters a syntax error. |
15 | TypeError |
Raised when an operation or function is applied to an object of inappropriate type. |
16 | ValueError |
Raised when an operation or function receives an argument that has the right type but an inappropriate value. |
17 | ZeroDivisionError |
Raised when the second argument of a division or modulo operation is zero. |
18 | FileExistsError |
Raised when trying to create a file or directory which already exists. |
19 | FileNotFoundError |
Raised 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:
# some code that could cause an exception
except:
# some code to execute to handle exception
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
orelse
clause. Again, the exception is re-raised after thefinally
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 messageexcs
: 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.
FAQs about Python Exception Handling
What is an exception in Python?
An exception in Python is an error or an unexpected event that occurs during the execution of a program. When an exception occurs, it disrupts the normal flow of the program.
Why is exception handling important in Python?
Exception handling is important because it allows your code to respond to errors in a controlled and graceful manner. It helps prevent program crashes and makes debugging and error analysis more manageable.
How do I handle exceptions in Python?
You handle exceptions in Python by using try
, except
, and optionally finally
blocks. The try
block contains the code that may raise an exception, and the except
block specifies what to do when an exception is raised.
What is the purpose of the finally
block?
The finally
block is used to execute code that should run regardless of whether an exception occurred or not. It’s often used for cleanup tasks, such as closing files or releasing resources.
Can I handle multiple exceptions in a single try-except
block?
Yes, you can handle multiple exceptions by specifying multiple except
blocks, each for a different exception type. You can also use a single except
block to catch multiple exceptions by providing a tuple of exception types.
Ini is a startup enthusiast, software engineer and technical writer. Flutter and Django are his favorite tools at the moment for building software solutions. He loves Afrobeats music.