Python has a complete set of built-in exceptions that provide a quick and efficient way to handle errors and exceptional situations that may happen in your code. Knowing the most commonly used built-in exceptions is key for you as a Python developer. This knowledge will help you debug code because each exception has a specific meaning that can shed light on your debugging process.
You’ll also be able to handle and raise most of the built-in exceptions in your Python code, which is a great way to deal with errors and exceptional situations without having to create your own custom exceptions.
In this tutorial, you’ll:
- Learn what errors and exceptions are in Python
- Understand how Python organizes the built-in exceptions in a class hierarchy
- Explore the most commonly used built-in exceptions
- Learn how to handle and raise built-in exceptions in your code
To smoothly walk through this tutorial, you should be familiar with some core concepts in Python. These concepts include Python classes, class hierarchies, exceptions, try
… except
blocks, and the raise
statement.
Get Your Code: Click here to download the free sample code that you’ll use to learn about Python’s built-in exceptions.
Take the Quiz: Test your knowledge with our interactive “Python's Built-in Exceptions: A Walkthrough With Examples” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python's Built-in Exceptions: A Walkthrough With ExamplesIn this quiz, you'll test your understanding of Python's built-in exceptions. With this knowledge, you'll be able to effectively identify and handle these exceptions when they appear. Additionally, you'll be more familiar with how to raise some of these exceptions in your code.
Errors and Exceptions in Python
Errors and exceptions are important concepts in programming, and you’ll probably spend a considerable amount of time dealing with them in your programming career. Errors are concrete conditions, such as syntax and logical errors, that make your code work incorrectly or even crash.
Often, you can fix errors by updating or modifying the code, installing a new version of a dependency, checking the code’s logic, and so on.
For example, say you need to make sure that a given string has a certain number of characters. In this case, you can use the built-in len()
function:
>>> len("Pythonista") = 10
File "<input>", line 1
...
SyntaxError: cannot assign to function call here.
Maybe you meant '==' instead of '='?
In this example, you use the wrong operator. Instead of using the equality comparison operator, you use the assignment operator. This code raises a SyntaxError
, which represents a syntax error as its name describes.
Note: In the above code, you’ll note how nicely the error message suggests a possible solution for correcting the code. Starting in version 3.10, the Python core developers have put a lot of effort into improving the error messages to make them more friendly and useful for debugging.
To fix the error, you need to localize the affected code and correct the syntax. This action will remove the error:
>>> len("Pythonista") == 10
True
Now the code works correctly, and the SyntaxError
is gone. So, your code won’t break, and your program will continue its normal execution.
There’s something to learn from the above example. You can fix errors, but you can’t handle them. In other words, if you have a syntax error like the one in the example, then you won’t be able to handle that error and make the code run. You need to correct the syntax.
On the other hand, exceptions are events that interrupt the execution of a program. As their name suggests, exceptions occur in exceptional situations that should or shouldn’t happen. So, to prevent your program from crashing after an exception, you must handle the exception with the appropriate exception-handling mechanism.
To better understand exceptions, say that you have a Python expression like a + b
. This expression will work if a
and b
are both strings or numbers:
>>> a = 4
>>> b = 3
>>> a + b
7
In this example, the code works correctly because a
and b
are both numbers. However, the expression raises an exception if a
and b
are of types that can’t be added together:
>>> a = "4"
>>> b = 3
>>> a + b
Traceback (most recent call last):
File "<input>", line 1, in <module>
a + b
~~^~~
TypeError: can only concatenate str (not "int") to str
Because a
is a string and b
is a number, your code fails with a TypeError
exception. Since there is no way to add text and numbers, your code has faced an exceptional situation.
Python uses classes to represent exceptions and errors. These classes are generically known as exceptions, regardless of what a concrete class represents, an exception or an error. Exception classes give us information about an exceptional situation and also errors detected during the program’s execution.
The first example in this section shows a syntax error in action. The SyntaxError
class represents an error but it’s implemented as a Python exception. This could be confusing, but Python uses exception classes for both errors and exceptions.
Another example of an exception could be when you’re working on a piece of code that processes a text file, and that file doesn’t exist. In this case, you don’t have an error in your code. You have an exceptional situation that you need to handle to prevent the program from crashing. You have no control over the problem because you can’t ensure that the file exists by modifying your code. You need to handle the exception.
You can use try
… except
blocks to handle exceptions in Python. In the following section, you’ll learn the basics of how to do that handling.
Handling Exceptions
If you have a piece of code that raises an exception, and you don’t provide a handler code for that exception, then your program will stop running. After that, an exception traceback will appear on the standard output, your screen.
Note: To learn the basics of exception handling in Python, check out the Python Exceptions: An Introduction tutorial.
In Python, you can handle exceptions using the try
… except
statement, which allows you to catch the exception and provide recuperative actions.
Consider the following example. A common exception that you’ll see when you start using Python’s lists and tuples is IndexError
. This exception happens when you try to access an index that’s out of range:
>>> numbers = [1, 2, 3]
>>> numbers[5]
Traceback (most recent call last):
...
IndexError: list index out of range
In this example, the list of numbers only has three values. So, when you try to access index 5
in an indexing operation, you get an IndexError
that breaks the code. You can wrap up this code in a try
… except
block to prevent the breakdown:
>>> try:
... numbers[5]
... except IndexError:
... print("Your list doesn't have that index 😔")
...
Your list doesn't have that index 😔
Now, the code doesn’t break with an exception. Instead, it prints a message to the screen. Note that the call to print()
is just a placeholder action for the sake of the example. In real-world code, you may do other things here.
The above example illustrates the most basic construct to handle exceptions in Python. You can check out the tutorial suggested above to dive deeper into exception handling. Now, it’s time to learn about the other side of the coin. You can also raise exceptions in Python.
Raising Exceptions
Python has the raise
statement as part of its syntax. You can use this statement to raise exceptions in your code as a response to exceptional situations.
Note: To dive deeper into raising exceptions in Python, check out the Python’s raise: Effectively Raising Exceptions in Your Code tutorial.
As an example of how to use the raise
statement, say that you need to write a function for calculating the average grade of students. You come up with the following function:
>>> def average_grade(grades):
... return sum(grades) / len(grades)
...
>>> average_grade([5, 4, 5, 3])
4.25
>>> average_grade([])
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
This function works okay. However, when the list of grades is empty, you get a zero division error because len(grades)
will be 0
. When the user of the code sees the error message, they may get confused. A zero division error? What’s causing this?
A better approach would probably be to make sure that the input list isn’t empty and raise a more appropriate exception if that’s the case:
>>> def average_grade(grades):
... if not grades:
... raise ValueError("empty grades not allowed")
... return sum(grades) / len(grades)
...
>>> average_grade([5, 4, 5, 3])
4.25
>>> average_grade([])
Traceback (most recent call last):
...
ValueError: empty grades not allowed
In this updated version of average_grade()
, you add a conditional statement that checks whether the input data is empty. If it is, you raise a ValueError
with an explicit error message that clearly communicates what’s wrong with the code.
The IndexError
and ValueError
exceptions are examples of commonly used built-in exceptions in Python. In the following sections, you’ll learn more about these and several other built-in exceptions.
This knowledge will help you in several ways. First, you’ll be able to quickly figure out the type of error you may have in your code, which improves your debugging skills. Second, you’ll be armed with a wide arsenal of already available exceptions to raise in your own code, freeing yourself from creating custom exceptions.
Python Built-in Exceptions
Python has over sixty built-in exceptions that represent a wide range of common errors and exceptional situations. These exceptions are organized into two groups:
The first group of exceptions comprises exception classes that are primarily used as base classes for other exceptions. For example, in this group, you have the Exception
class, which is specially designed to allow you to create custom exceptions.
The second group contains exceptions that you’ll commonly see in Python code or get while executing Python code. For example, you’ve probably seen some of the following concrete exceptions in your day-to-day coding:
Exception Class | Description |
---|---|
ImportError |
Appears when an import statement can’t load a module |
ModuleNotFoundError |
Happens when import can’t locate a given module |
NameError |
Appears when a name isn’t defined in the global or local scope |
AttributeError |
Happens when an attribute reference or assignment fails |
IndexError |
Occurs when an indexing operation on a sequence uses an out-of-range index |
KeyError |
Occurs when a key is missing in a dictionary or another mapping |
ZeroDivisionError |
Appears when the second operand in a division or modulo operation is 0 |
TypeError |
Happens when an operation, function, or method operates on an object of inappropriate type |
ValueError |
Occurs when an operation, function, or method receives the right type of argument but the wrong value |
This table is just a small sample of Python’s built-in exceptions. You can find a comprehensive list of all the built-in exceptions in the Built-in Exceptions page of Python’s documentation.
Glancing at the Exceptions Hierarchy
As you already know, you’ll find many built-in exceptions in Python. You can explore them by inspecting the builtins
namespace from a REPL session:
>>> import builtins
>>> dir(builtins)
[
'ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
...
]
In this example, you first import the builtins
namespace. Then, you use the built-in dir()
function to list the names that this module defines. Note that you’ll get the complete list of built-in names. Between them, you’ll find the built-in exceptions.
Python’s built-in exceptions are coded as classes and organized in a class hierarchy that includes the following levels:
- Base classes: They provide the base classes for other exceptions. You should only use them as parent classes. However, you might find some of these exceptions, such as the
Exception
class, in some codebases. - Concrete exceptions: They’re exceptions that Python will raise as a response to different exceptional situations. They also provide a great base of concrete exceptions that you can raise in your own code when appropriate.
- OS exceptions: They provide exceptions that the operating system generates. Python passes them along to your application. In most cases, you’ll be catching these exceptions but not raising them in your code.
- Warnings: They provide warnings about unexpected events or actions that could result in errors later. These particular types of exceptions don’t represent errors. Ignoring them can cause you issues later, but you can ignore them.
A diagram of the exception hierarchy is below:
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ExceptionGroup [BaseExceptionGroup]
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── EncodingWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
Note that most classes in the hierarchy inherit from Exception
. This is also the base class that you should use in those situations when you need to create a custom exception.
Knowing the Base Exceptions
In the built-in exception hierarchy, you’ll find a few classes that are designed to be base classes. The BaseException
class is on the top. Then you have five subclasses:
Exception Base Class | Description |
---|---|
BaseExceptionGroup |
Creates an exception group that wraps any exception rather than only those that inherit from Exception |
GeneratorExit |
Occurs when a generator or coroutine is closed |
KeyboardInterrupt |
Happens when the user presses the interrupt key combination, which normally is Ctrl+C |
SystemExit |
Results from calling the sys.exit() function, but you can also raise it directly |
Exception |
Provides a base class for user-defined exceptions, which should be derived from this class |
As you can see, all of these base exceptions have their specific use case. It’s important to note that you should use Exception
and not BaseException
when you create custom exceptions. That’s because BaseException
should be used for exceptions that should never be caught in real code.
In practice, when catching and raising exceptions, you should use the most specific exception for the problem at hand.
For example, if you have a piece of code that can potentially raise a ValueError
, then you should explicitly handle this exception. If you use Exception
instead of ValueError
, then your code will catch Exception
and all its subclasses, including ValueError
. If your code ends up raising something different from ValueError
, then that error will be mishandled.
Getting to Know Warnings
At the bottom of the exception hierarchy, you’ll find warnings. These are particular types of exceptions that denote something that can cause problems in the near future. Warnings are exceptions that pertain to the warning categories.
Note: Because warnings are a type of exception with specific use cases and dedicated documentation, you won’t cover them in detail in this tutorial. You can check the Warning control page in Python’s documentation for a complete walkthrough of warnings.
Probably the most common warning that you’ll see while executing Python code is DeprecationWarning
. This warning appears when you use deprecated features of the language. For example, Python 3.12 has deprecated the calendar.January
and calendar.February
constants:
>>> # Python 3.12.2
>>> calendar.January
<stdin>:1: DeprecationWarning: The 'January' attribute is deprecated,
use 'JANUARY' instead
1
Even though the code works because the constants haven’t been removed yet, Python lets you know that the feature is deprecated to prevent you from having issues in the future. As the warning message says, now you should use calendar.JANUARY
.
Syntax Errors
The first exception that you’ll probably see in Python is the SyntaxError
exception. Even though Python uses an exception class for this type of issue, you should be clear that they represent errors rather than exceptions. So you won’t be handling them but fixing them, as you already learned.
You’ll also find that Python defines a couple of additional exceptions that inherit from SyntaxError
:
These exceptions can be expected when you’re starting to learn Python, and they can confuse you. Fortunately, modern code editors and IDE include features that spot and often remove the conditions that generate these exceptions. In the following sections, you’ll learn about this group of exceptions.
SyntaxError
When Python detects an invalid syntax in a piece of code, it raises a SyntaxError
exception. The exception prints a traceback with helpful information that you can use to debug the error and fix your code.
Note: To learn more about syntax errors, check out the Invalid Syntax in Python: Common Reasons for SyntaxError tutorial.
There are many situations where you can end up with a syntax error in your code. You can forget a comma or a closing bracket, misuse an operator or a keyword, and more.
Here are a few examples:
>>> numbers = [1, 2, 3 4]
File "<stdin>", line 1
numbers = [1, 2, 3 4]
^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> 7 = 7
File "<stdin>", line 1
7 = 7
^
SyntaxError: cannot assign to literal here.
Maybe you meant '==' instead of '='?
>>> class = "Economy"
File "<stdin>", line 1
class = "Economy"
^
SyntaxError: invalid syntax
These are common syntax errors that you can fall into at times. The good news is that with the improved error messages that Python provides these days, you can quickly track down the error and fix it.
IndentationError
Unlike other programming languages, indentation is part of Python syntax. To delimit a code block in Python, you use indentation. Therefore, you may get an error if the indentation isn’t correct. Python uses the IndentationError
exception to denote this problem.
You may encounter this exception when you’re starting to learn Python. It can also appear when you copy and paste code from intricate places. Fortunately, the error isn’t common nowadays because modern code editors can automatically fix code indentation.
To see the exception in action, consider the following example of a function whose code block uses non-uniform indentation:
>>> def greet(name):
... print(f"Hello, {name}!")
... print("Welcome to Real Python!")
File "<stdin>", line 3
print("Welcome to Real Python!")
^
IndentationError: unindent does not match any outer indentation level
In this example, the highlighted lines have different indentations. The first line is indented at four spaces, while the second line is indented at two spaces. This indentation mismatch triggers an IndentationError
. Again, because this is a syntax error, Python reports it immediately. You can quickly fix the issue by correcting the indentation.
TabError
Another possible issue for those who come from other programming languages is to mix tab and space characters while indenting Python code. The exception for this issue is TabError
, which is a subclass of IndentationError
. So, it’s another syntax error exception.
Note: The Style Guide for Python Code (PEP 8) explicitly says:
Spaces are the preferred indentation method. Tabs should be used solely to remain consistent with code that is already indented with tabs. Python disallows mixing tabs and spaces for indentation. (Source)
Here’s an example where Python raises a TabError
exception:
greeting.py
def greet(name):
print(f"Hello, {name}!")
print("Welcome to Real Python!")
In this example, the first line in greet()
is indented using a tab character, while the second line is indented using eight spaces, which is the common number of spaces that replaces a tab character.
Note: If you don’t use eight spaces in the second line, then you’ll get an IndentationError
instead of a TabError
.
To try out the example, go ahead and run the file from the command line:
$ python greeting.py
File ".../greeting.py", line 3
print("Welcome to Real Python!")
TabError: inconsistent use of tabs and spaces in indentation
Because the code indentation mixes tabs and spaces, you get a TabError
indentation error. Again, TabError
exceptions aren’t common nowadays because modern code editors can fix them automatically. So, if you have your editor well-set for Python development, then you probably won’t see this exception in action.
Import-Related Exceptions
Sometimes, when importing packages, modules and their contents, your code fails with an error telling you that the target file wasn’t found. In practice, you can get one of two import-related exceptions:
Note that the ModuleNotFoundError
exception is a subclass of ImportError
with a more specific meaning or goal, as you’ll learn in a moment.
In the following sections, you’ll learn about these two exceptions and when Python raises them. This knowledge will help you fix the problem and make your code work.
ModuleNotFoundError
As you’ve already learned, the ModuleNotFoundError
exception is a subclass of ImportError
with a more specific goal. Python raises this exception when it can’t find the module from which you want to import something:
>>> import non_existing
Traceback (most recent call last):
...
ModuleNotFoundError: No module named 'non_existing'
When Python doesn’t find the target module in its import search path, it raises a ModuleNotFoundError
exception. To fix this problem, you must ensure your module is listed in sys.path
.
Note: The best way to ensure that your module is available in Python’s path is to install it. You can use pip
and install your local packages similarly to how you install packages from PyPI.
Because ModuleNotFoundError
is a subclass of ImportError
, when you explicitly use the latter in a try
… except
block, you’ll be catching both exceptions. So, if your intention is to catch those situations where the target module isn’t present, then you should be specific and use ModuleNotFoundError
.
ImportError
Python raises the ImportError
exception for all the import-related issues that aren’t covered by ModuleNotFoundError
. This can happen for two main reasons:
- An
import module
statement fails to load a module for a reason not covered byModuleNotFoundError
. - A
from module import name
statement fails to findname
in the target module.
Now go ahead and run the following import
statement to see the ImportError
exception in action:
>>> from sys import non_existing
Traceback (most recent call last):
...
ImportError: cannot import name 'non_existing' from 'sys' (unknown location)
In this example, you try to import the non_existing
name from the sys
module. Because the name doesn’t exist in this module, you get an ImportError
. You can quickly fix the problem by providing the correct target name.
Note: To dive deeper into how imports work in Python, check out the Python import: Advanced Techniques and Tips tutorial.
In real-world code, you can take advantage of ImportError
or ModuleNotFoundError
to optionally load different modules or libraries that provide a given functionality depending on the library availability.
For example, say you need to parse a TOML file and read its content. In this case, you can use the standard-library module tomllib
if you’re using Python 3.11 or later. Otherwise, you should use the third-party library tomli
, which is compatible with tomllib
.
Here’s how you can do this:
try:
import tomllib # Python >= 3.11
except ModuleNotFoundError:
import tomli as tomllib # Python < 3.11
The import in the try
clause targets the standard-library module tomllib
. If this import raises an exception because you’re using a Python version lower than 3.11, then the except
clause imports the third-party library tomli
, which you need to install as an external dependency of your project.
Lookup Error Exceptions
Getting an IndexError
when you perform indexing operations on a sequence, or a KeyError
when you look up keys in dictionaries, are also common issues in Python. In the following sections, you’ll learn about these two exceptions and when they can happen in your code.
IndexError
The IndexError
exception happens when you try to retrieve a value from a sequence using an out-of-range index:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet",
... ]
>>> colors[10]
Traceback (most recent call last):
File "<input>", line 1, in <module>
colors[10]
~~~~~~^^^^
IndexError: list index out of range
In this example, you use 10
as the index to get a value from your colors
list. Because the list only has seven items, the valid indices go from 0
to 6
. Your target index is out of range, and Python gives you an IndexError
exception.
IndexError
can be a frequent exception in Python, especially when you’re using dynamically generated indices. To fix the problem, you need to figure out which index your code is using to retrieve a value from the target sequence and then adjust the index range to correct the issue.
Note: In most situations, you won’t use indices in Python loops. Because of this practice, the index error issue is less common in Python loops than in other programming languages where loops rely on indices.
If you can’t control the range of indices, then you can catch the exception in a try
… except
block and take the appropriate recovery action.
You can also raise the IndexError
exception in your own code. This exception can be appropriate when you’re creating custom sequence-like data structures. For example, say that you need to create a sequence-like stack:
stack.py
class Stack:
def __init__(self, items=None):
self.items = list(items) if items is not None else []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __len__(self):
return len(self.items)
def __getitem__(self, index):
try:
return self.items[index]
except IndexError:
raise IndexError(
f"your stack only has {len(self)} items!"
) from None
In this example, you define Stack
and provide the .push()
and .pop()
methods. The former method appends a new item to the top of the stack, while the latter removes and returns the item at the top of the stack.
Then, you have the .__len__()
special method to support the built-in len()
function, which gives you the number of items in the stack.
Finally, you have the .__getitem__()
method. This special method takes an index as an argument and returns the item at the input index. The try
… except
block catches the IndexError
exception and reraises it with a more specific error message. You don’t rely on the original message, which makes your code more focused.
KeyError
Similarly, when you try to get a non-existing key from a dictionary, then you get a KeyError
exception. This issue is also common in Python:
>>> fruits = {"apple": 0.40, "orange": 0.35, "banana": 0.25}
>>> fruits["grape"]
Traceback (most recent call last):
File "<input>", line 1, in <module>
fruits["grape"]
~~~~~~^^^^^^^^^
KeyError: 'grape'
In this example, you try to get the grape
key from the fruits
dictionary and get a KeyError
exception. In this case, the error message is pretty short. It only displays the name of the missing key.
Again, using dynamically generated keys can be the source of the problem. So, to correct the issue, you’ll need to check the generated and existing keys in your code. Alternatively, you can use the .get()
method with an appropriate default value if that’s a suitable solution for your use case.
Finally, you can also raise a KeyError
in your code. This could be helpful when you need to create a dictionary-like class and provide a more descriptive error message for your users.
Name Errors: NameError
The NameError
exception is also pretty common when you’re starting with Python. This exception happens when you try to use a name that’s not present in your current namespace. So, this exception is closely related to scopes and namespaces.
For example, say that you’re working in a REPL session. You’ve imported the sys
module to use it in your current task. For some reason, you restart the interactive session and try to use sys
without re-importing it:
>>> sys.path
Traceback (most recent call last):
File "<input>", line 1, in <module>
sys.path
^^^
NameError: name 'sys' is not defined. Did you forget to import 'sys'?
Because you restarted the interactive session, all the names and modules that you imported before are gone. Now, when you try to use the sys
module, you get a NameError
.
Note that if the unknown name is after the dot in a qualified name, you won’t see a NameError
. In this case, you’ll get an AttributeError
exception instead, which you’ll learn about in the following section.
Object-Related Exceptions
You’ll find a few exceptions that can occur when working with Python classes, objects, and built-in types. The most common ones are the following:
In the following sections, you’ll learn when these exceptions happen and how to deal with them in your Python code.
TypeError
Python raises a TypeError
exception when you apply an operation or function to an object that doesn’t support that operation. For example, consider calling the built-in len()
function with a number as an argument:
>>> len(42)
Traceback (most recent call last):
File "<input>", line 1, in <module>
len(42)
TypeError: object of type 'int' has no len()
In this example, the argument to len()
is an integer number, which doesn’t support the function. Therefore, you get an error with an appropriate message.
In practice, you can catch the TypeError
exception in your own code when you need to prevent a type-related issue. You can also raise the exception in your code to denote a type-related problem. For example, say that you want to write a function that takes an iterable of numbers and returns a list of squared values. You want to make sure that the function only accepts iterable objects.
In this situation, you can have a function like the following:
>>> def squared(numbers):
... try:
... iter(numbers)
... except TypeError:
... raise TypeError(
... f"expected an iterable, got '{type(numbers).__name__}'"
... ) from None
... return [number**2 for number in numbers]
...
>>> squared([1, 2, 3, 4, 5])
[1, 4, 9, 16, 25]
>>> squared(42)
Traceback (most recent call last):
...
TypeError: expected an iterable, got 'int'
In this example, your function uses the built-in iter()
function to check the input data. This function raises a TypeError
exception if the provided input isn’t iterable. You catch this exception and reraise it but with a custom error message that adheres to your code’s purpose. Then, you build the list of squared values using a list comprehension.
ValueError
Similarly, Python raises the ValueError
exception when an operation or function gets an argument that has the right type but an inappropriate value:
>>> float("1")
1.0
>>> float("one")
Traceback (most recent call last):
...
ValueError: could not convert string to float: 'one'
In this example, you first use the built-in float()
function to convert a string to a floating-point number. The input string contains a numeric value, so the operation succeeds.
In the second example, you call the same function using a string that doesn’t represent a valid number and get a ValueError
exception. Note that the input argument is a string, which is the correct type of argument. However, the input string isn’t a valid numeric value, so you have the right type but the wrong value.
You can also use ValueError
in your Python code. This exception can be appropriate in different situations. For example, say that you need to write a class to represent rainbow colors. This class has seven allowed color names, which you can represent with strings:
rainbow.py
COLORS = {
"Red": {"Hex": "#FF0000", "RGB": (255, 0, 0)},
"Orange": {"Hex": "#FF7F00", "RGB": (255, 127, 0)},
"Yellow": {"Hex": "#FFFF00", "RGB": (255, 255, 0)},
"Green": {"Hex": "#00FF00", "RGB": (0, 255, 0)},
"Blue": {"Hex": "#0000FF", "RGB": (0, 0, 255)},
"Indigo": {"Hex": "#4B0082", "RGB": (75, 0, 130)},
"Violet": {"Hex": "#8B00FF", "RGB": (139, 0, 255)},
}
class RainbowColor:
def __init__(self, name="Red"):
name = name.title()
if name not in COLORS:
raise ValueError(f"{name} is not a valid rainbow color")
self.name = name
def as_hex(self):
return COLORS[self.name]["Hex"]
def as_rgb(self):
return COLORS[self.name]["RGB"]
In this example, you first define a COLORS
constant that holds the rainbow colors and their corresponding Hex and RGB representation.
Then, you define the RainbowColor
class. In the class initializer, you use a conditional to make sure that the input color name is one of the rainbow colors. If that’s not the case, then you raise a ValueError
because the color name is of the correct type but has the wrong value. It’s a string but not a valid color name.
Here’s how the class works in practice:
>>> from rainbow import RainbowColor
>>> color = RainbowColor("Gray")
Traceback (most recent call last):
...
ValueError: Gray is not a valid rainbow color
>>> color = RainbowColor("Blue")
>>> color.name
'Blue'
>>> color.as_rgb()
(0, 0, 255)
In this code snippet, when you try to pass a color name that’s not allowed, then you get a ValueError
. If the color name is valid, then the class works fine.
AttributeError
Another common exception that you’ll see when you work with Python classes is AttributeError
. This exception occurs when the specified object doesn’t define the attribute or method that you’re trying to access.
Take, for example, your RainbowClass
from the previous section. This class has one attribute and two methods, .name
, .as_hex()
, and .as_rgb()
. If you access them, then you’ll have a result according to what they do. However, say that you try to access a method called .as_hsl()
. What would happen?
Here’s the answer:
>>> from rainbow import RainbowColor
>>> color = RainbowColor("Blue")
>>> color.as_hsl()
Traceback (most recent call last):
...
AttributeError: 'RainbowColor' object has no attribute 'as_hsl'
Because RainbowColor
doesn’t define the .as_hsl()
method, you get an AttributeError
that tells you the class doesn’t have an attribute that matches that name.
This error can be common in complex class hierarchies that define similar classes with slightly different interfaces. In this situation, you may think that a given class defines a given method or attribute, but that may not be the case. To fix the issue, you can take a look at the class definition or documentation to make sure that you only use the attributes and methods that the class defines.
You can skip the AttributeError
exception by using the built-in hasattr()
function:
>>> if hasattr(color, "as_hsl"):
... color.as_hsl()
... else:
... print("Hmm, you can't call that.")
...
Hmm, you can't call that.
In this example, you use hasattr()
to explicitly check if the color
object has an attribute called "as_hsl"
.
You can also catch and raise the AttributeError
exception in your custom classes. In practice, this exception can be pretty useful because Python encourages you to use a coding style based on handling exceptions called EAFP (easier to ask forgiveness than permission).
In short, using this style means that you decide to handle errors and exceptions when they happen. In contrast, with the LBYL (look before you leap) style, you try to prevent errors and exceptions before they happen. That’s what you did in the example above.
So, instead of relying on hasattr()
, you should write the above example as follows:
>>> try:
... color.as_hsl()
... except AttributeError:
... print("Hmm, you can't call that.")
...
Hmm, you can't call that.
This coding style is more direct. You go and call the desired method. If that fails with an AttributeError
, then you perform alternative actions.
Finally, you’ll also get an AttributeError
exception when you try to access an object that’s not defined in an already imported module:
>>> import sys
>>> sys.non_existing
Traceback (most recent call last):
...
AttributeError: module 'sys' has no attribute 'non_existing'
Note that you don’t get an import error in this example. Instead, you get an AttributeError
exception. That’s because the objects defined in a module become attributes of that module.
Arithmetic Error Exceptions
The ArithmeticError
exception class is the base class for built-in exceptions that are related to three different types of arithmetic errors:
Of these three exceptions, the most common is ZeroDivisionError
. The FloatingPointError
is defined as a built-in exception, but Python doesn’t use it nowadays. In the following sections, you’ll learn about when the ZeroDivisionError
and the OverflowError
exceptions may appear in your code.
ZeroDivisionError
Python raises the ZeroDivisionError
exception when a division’s divisor or right operand is zero. As a quick example, say that you need to write a function to compute the average grade of a group of students. In this case, you can come up with the following function:
>>> def average_grade(grades):
... return sum(grades) / len(grades)
...
>>> average_grade([4, 3, 3, 4, 5])
3.8
This function works okay when you use appropriate input data. But what would happen if you called the function with an empty list object? Here’s the answer:
>>> average_grade([])
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Because the input list is empty, the len()
function returns 0
. This return value produces a ZeroDivisionError
exception when the actual division occurs.
To fix the issue, you can catch the exception and present the user with a descriptive error message:
>>> def average_grade(grades):
... try:
... return sum(grades) / len(grades)
... except ZeroDivisionError:
... raise ValueError(
... "you should pass at least one grade to average_grade()"
... ) from None
...
>>> average_grade([])
Traceback (most recent call last):
...
ValueError: you should pass at least one grade to average_grade()
In this updated version of average_grade()
, you catch the ZeroDivisionError
that an empty input list generates and raise a ValueError
with a user-friendly error message.
Finally, the ZeroDivisionError
exception also occurs when the right-hand operand on a modulo operation is zero:
>>> 42 % 0
Traceback (most recent call last):
File "<input>", line 1, in <module>
42 % 0
~~~^~~
ZeroDivisionError: integer modulo by zero
The modulo operator returns the remainder of dividing two numbers. Because the division of the numbers is the first step to computing the remainder, you can get a ZeroDivisionError
if the right-hand operand is 0
.
OverflowError
The OverflowError
isn’t that common. Python raises this exception when the result of an arithmetic operation is too large, and there’s no way to represent it:
>>> 10.0**1000
Traceback (most recent call last):
File "<input>", line 1, in <module>
10.0**1000
~~~~^^~~~~
OverflowError: (34, 'Result too large')
In this example, the number that results from the power operation is too high, and Python can’t represent it correctly. So, you get an OverflowError
exception.
Iteration-Related Exceptions
Python has a few built-in exceptions that are closely related to iteration. The RecursionError
is related to recursive code and is what you can call a standard exception. Then you have the StopIteration
exception, which Python internally uses to control the execution flow of for
loops. This exception has an asynchronous counterpart called AsyncStopIteration
, which Python uses in async for
loops.
Note: The AsyncStopIteration
exception has the same semantic meaning as the StopIteration
exception but for asynchronous loops. So, this exception won’t be covered in this tutorial.
In the following sections, you’ll learn about the RecursionError
and StopIteration
exceptions and how Python uses them.
RecursionError
A function that calls itself is known as a recursive function. These types of functions are the base of an iteration technique called recursion.
Note: To learn more about recursion, check out the Recursion in Python: An Introduction tutorial.
Consider the following toy function as an example of a recursive function:
>>> def recurse():
... recurse()
...
This function doesn’t follow the rules of recursion because it doesn’t have a base case that stops the repetition. It only has a recursive case. So, if you call the function, then you get a RecursionError
exception:
>>> recurse()
Traceback (most recent call last):
File "<input>", line 1, in <module>
recurse()
File "<input>", line 2, in recurse
recurse()
File "<input>", line 2, in recurse
recurse()
File "<input>", line 2, in recurse
recurse()
[Previous line repeated 981 more times]
RecursionError: maximum recursion depth exceeded
Python will raise a RecursionError
exception when the interpreter detects that the maximum recursion depth is exceeded. This may happen when your base case doesn’t work properly or when the required number of recursions for performing the computation exceeds the recursion limit.
Note: A recursion limit is necessary because every function call occupies memory on your system’s call stack, so it’s a way to control memory usage.
You can use the sys.getrecursionlimit()
function to check your current recursion depth:
>>> import sys
>>> sys.getrecursionlimit()
1000
The result of calling this function is a number representing how many times a recursive function can call itself in Python. If appropriate for your use case, you can also set a different recursion limit using the sys.setrecursionlimit()
function.
StopIteration
Python raises a StopIteration
exception when you call the built-in next()
function on an exhausted iterator. Its purpose is to signal that there are no more items in the iterator. So, Python uses this exception internally to control iteration.
Consider the following toy example:
>>> numbers_iterator = iter([1, 2, 3])
>>> next(numbers_iterator)
1
>>> next(numbers_iterator)
2
>>> next(numbers_iterator)
3
>>> next(numbers_iterator)
Traceback (most recent call last):
File "<input>", line 1, in <module>
next(numbers_iterator)
StopIteration
In this example, you use the built-in iter()
function to create an iterator from a short list of numbers. Then, you call next()
repeatedly to consume the iterator. The fourth call to this function raises a StopIteration
exception to signal that the iterator has no more items to retrieve.
Internally, Python uses this exception to terminate for
loops. In other words, when a for
loop is iterating over an iterator, the loop stops when it gets the StopIteration
exception. Because of this, it’s not common to see this exception in action. Note that Python’s for
loop calls next()
under the hood to perform the iteration.
In practice, you can use the StopIteration
exception in your code as part of an implementation of the iterator protocol. This protocol consists of two special methods:
.__iter__()
is called to initialize the iterator. It must return an iterator object..__next__()
is called to iterate over the iterator. It must return the next value in the data stream.
When you provide these methods in a custom class, then your class supports iteration. To signal when the iteration must stop, you must raise the StopIteration
exception in the .__next__()
method.
Note: To learn more about iterators in Python, check out the Iterators and Iterables in Python: Run Efficient Iterations tutorial.
To illustrate how to use StopIteration
, say that you need to create a custom class that takes a list of values and creates an iterator over their squares:
squares.py
class SquareIterator:
def __init__(self, values):
self.values = values
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.values):
raise StopIteration
square = self.values[self.index] ** 2
self.index += 1
return square
In this class, the initializer takes a list of values as an argument. It also defines a reference index, which starts at 0
. The .__iter__()
method returns self
, which is common practice when you’re using the iterator protocol like in this example.
In the .__next__()
method, you check if the current index is greater than or equal to the number of values in the input list. If that’s the case, then you raise a StopIteration
to signal that the iterator is exhausted. Next, you compute the square of the current value and return it as the method’s result.
Now you can use instances of this class in a for
loop:
>>> from squares import SquareIterator
>>> for value in SquareIterator([1, 2, 3]):
... print(value)
...
1
4
9
Your iterator works fine. It generates square values and supports iteration. Note that you don’t see the StopIteration
exception happening. However, Python uses the exception internally to terminate the loop when the iterator is done.
File-Related Exceptions
When processing files in your Python code, there are several different problems you may face. Your code may try to create a file that already exists, access a non-existing file, run a file operation on a directory rather than a file, or have permission issues. Python has file-related exceptions that flag these situations.
Here are some of the most popular ones:
In the following sections, you’ll learn about these built-in exceptions and when they occur in Python.
FileExistsError
When you try to create a file or directory that already exists, Python raises the built-in FileExistsError
exception.
To see this exception in action, say that you need to create a file in your working directory and write some text to it. However, you want to avoid replacing the file if it already exists. In this case, you can take advantage of the FileExistsError
and write the following code:
hello.py
try:
with open("hello.txt", mode="x") as file:
file.write("Hello, World!")
except FileExistsError:
print("The file already exists")
In this example, you use the built-in open()
function to open a file called hello.txt
. The "x"
mode means that you want to open the file for exclusive creation, failing if the file already exists.
Now go ahead and run this hello.py
file from your command line:
$ python hello.py
This command won’t issue any output. However, if you look at your working directory, then you’ll see a new file called hello.txt
with the text "Hello, World!"
in it.
If you run the command again, then the result will be different:
$ python hello.py
The file already exists
This time, because the hello.txt
file already exists, the file-creation code raises a FileExistsError
exception, and the except
clause prints an error message on the screen.
FileNotFoundError
Python raises the FileNotFoundError
exception when a file or directory is requested but doesn’t exist in the target location. For example, say that you’ve accidentally removed the hello.txt
file that you created in the previous section. When you try to read the file’s content, you get an error:
>>> with open("hello.txt", mode="r") as file:
... print(file.read())
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
with open("hello.txt", mode="r") as file:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'hello.txt'
In this code snippet, you try to open the hello.txt
file to read its content. Because you removed the file prior to this operation, your code fails with the FileNotFoundError
exception.
To handle this issue, you can rewrite your code as in the following example:
>>> try:
... with open("hello.txt", mode="r") as file:
... print(file.read())
... except FileNotFoundError:
... print("The file doesn't exist")
...
The file doesn't exist
Now, you use a try
… except
block to handle the FileNotFoundError
exception and prevent your code from breaking out.
PermissionError
Another common issue when processing files is when you try to access a file or directory without adequate access permissions. In this case, Python raises the PermissionError
exception to let you know that you don’t have the required permissions.
For example, say you’re working on a Unix-like system, such as Linux or macOS. You want to update the content of the /etc/hosts
file. In Unix-like systems, only the root user can modify this file. So, if you try to do your task using a different user, then you’ll get an error:
>>> with open("/etc/hosts", mode="a") as file:
... print(file.write("##"))
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
with open("/etc/hosts", mode="a") as file:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/etc/host'
This error occurs because the file /etc/hosts
is a system file. You need root permissions to modify this file. Your code tries to open the file for appending content without having the necessary permissions, which leads to a PermissionError
exception.
Abstract Classes-Related Exception: NotImplementedError
Sometimes, you want to create a custom base class with a predefined interface from which your users can derive their own classes. This way, you ensure that the subclasses fulfill the required interface. In Python, you can do this using what’s known as an abstract base class (ABC).
The abc
module in the standard library provides ABC
class and other related tools that you can use to define custom base classes that define a specific interface. Note that you can’t instantiate ABCs directly. They’re intended to be subclassed.
When you start to create your ABCs, you’ll find that when you define methods, it’s a common practice to raise a NotImplementedError
exception for those methods that the subclasses should implement but aren’t a strict requirement.
As an example, say that you want to create a class hierarchy to represent different birds, such as a duck, swan, penguin, and so on. You decide that all the classes should have the .swim()
and .fly()
methods. In this situation, you can start with the following base class:
birds.py
from abc import ABC
class Bird(ABC):
def swim(self):
raise NotImplementedError("must be implemented in subclasses")
def fly(self):
raise NotImplementedError("must be implemented in subclasses")
In this Bird
class, you inherit from abc.ABC
, which means you’re building an abstract base class. Then, you define the .swim()
and .fly()
methods, which raise the NotImplementedError
exception with an appropriate error message.
Note: You may also import abstractmethod
from abc
and decorate .swim()
and .fly()
with @abstractmethod
. Python will raise an error if you instantiate any class with an abstract method.
Here’s an example of how you can derive concrete birds from the above class:
birds.py
# ...
class Duck(Bird):
def swim(self):
print("The duck is swimming")
def fly(self):
print("The duck is flying")
class Penguin(Bird):
def swim(self):
print("The penguin is swimming")
In this example, you create the Duck
class with the .swim()
and .fly()
methods. Then, you create the Penguin
class, which only has a .swim()
method because penguins can’t fly. You can use the Duck
class normally. In contrast, the Penguin
class will behave differently when you call its .fly()
method:
>>> from birds import Duck
>>> donald = Duck()
>>> donald.swim()
The duck is swimming
>>> donald.fly()
The duck is flying
>>> skipper = Penguin()
>>> skipper.swim()
The penguin is swimming
>>> skipper.fly()
Traceback (most recent call last):
...
NotImplementedError: must be implemented in subclasses
In this code snippet, the Duck
class works as expected. Meanwhile, the Penguin
class raises a NotImplementedError
when you call the .fly()
method on one of its instances.
Assertion Errors: AssertionError
Python has a feature called assertions that you can define using the assert
statement. Assertions allow you to set sanity checks during the development of your code. Assertions allow you to test the correctness of your code by checking if some specific conditions remain true. This comes in handy while you’re testing and debugging your code.
Note: To dive deeper into the assert
statement, check out the Python’s assert: Debug and Test Your Code Like a Pro tutorial.
The assert statement has the following syntax:
assert expression[, assertion_message]
The expression
part is a condition that should be true unless you have a bug in your program. If the condition becomes false, then the assertion raises an AssertionError
exception and terminates the execution of your program.
Assertions are useful for writing test cases. You can use them to check assumptions like preconditions and postconditions. For example, you can test whether an argument is of a given type, you can test the return value of a function, and so on. These checks can help you catch errors as soon as possible when you’re developing a program.
Note: You shouldn’t rely on assertions to check assumptions in production code because assertions are turned off when your code runs in optimized mode using Python’s -O
or -OO
command-line options.
As an example, say that you’re in a Python coding interview. You’re asked to implement a function that tackles the FizzBuzz challenge, where you return "fizz"
for numbers divisible by three, "buzz"
for those divisible by five, and "fizz buzz"
for those divisible by both three and five. You write a function like the following:
fizzbuzz.py
def fizzbuzz(number):
if number % 3 == 0:
return "fizz"
elif number % 5 == 0:
return "buzz"
elif number % 15 == 0:
return "fizz buzz"
else:
return number
This function apparently covers all the possible scenarios. However, you decide to write a few basic tests to make sure that the function works correctly. You end up with the following code at the end of your fizzbuzz.py
file:
fizzbuzz.py
# ...
if __name__ == "__main__":
assert fizzbuzz(9) == "fizz"
assert fizzbuzz(10) == "buzz"
assert fizzbuzz(15) == "fizz buzz"
assert fizzbuzz(7) == 7
print("All tests pass")
In this code snippet, you’ve added some quick assertions to check whether your function returns the correct string with different input values. Now, you can run the test by executing the file from the command line:
$ python fizzbuzz.py
Traceback (most recent call last):
File ".../fizzbuzz.py", line 26, in <module>
assert fizzbuzz(15) == "fizz buzz"
^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Wow! You’ve gotten an AssertionError
exception, which means that some of the tests failed. When you look at the exception traceback, you note the assertion fails when you call the function with 15
as an argument. This number is divisible by 3
and by 5
, so the current order of your conditions is incorrect.
You must move the condition number % 15 == 0
to the first position:
fizzbuzz.py
def fizzbuzz(number):
if number % 15 == 0:
return "fizz buzz"
elif number % 3 == 0:
return "fizz"
elif number % 5 == 0:
return "buzz"
else:
return number
# ...
The conditions first check whether the input number is divisible by 3
and 5
. Now go ahead and run the file again:
$ python fizzbuzz.py
All tests pass
Great! All your tests pass. The function does its job correctly. That’s the power of the assert
statement.
Finally, note that you shouldn’t explicitly raise the AssertionError
exception in your code. Instead, you should let the assert
statement raise this exception when the assertion’s condition fails. Additionally, you shouldn’t attempt to handle errors by writing code that catches the AssertionError
exception because assertions can be disabled.
Interpreter Exit Exceptions
While writing Python code, you’ll find situations where you’ll need to exit or terminate a running application or program.
For example, if an error occurs while an app is running, you can decide to cleanly exit the app with an appropriate exit status, which can be helpful in command-line apps. Another example is when you’re testing some code that takes too long to run, or it’s somehow hanged. In this case, you’d like a quick way to terminate the code’s execution.
Python has the following exceptions that deal with these situations:
In general, your code shouldn’t be catching or handling these exceptions. Doing that can prevent you from exiting your program, making it impossible to quit from within the code or using the keyboard.
Note: Suddenly terminating a program with a SystemExit
or KeyboardInterrupt
may cause some undesired messy conditions in your system, such as leftover temporary files and open network or database connections.
You can use the atexit
module from the standard library to manage how your code responds to this practice. This module allows you to register and unregister cleanup functions that will automatically execute upon normal termination of the Python interpreter.
In the following sections, you’ll learn when these exceptions can happen in your Python code. You’ll also write a couple of examples that illustrate their use.
SystemExit
Python raises the SystemExit
exception when you call the sys.exit()
function in your code. When you don’t handle the exception, the Python interpreter exits without printing an exception traceback:
>>> import sys
>>> sys.exit(0)
$
In this example, you call the sys.exit()
to terminate the Python interpreter. This call takes you back to your terminal session.
In practice, you can use SystemExit
directly when you need to terminate an app. For example, say that you want to create a minimal CLI (command-line interface) app that mimics the basic functionality of the Unix ls
command, which lists the content of a given directory.
In this situation, you can write a script like the following:
ls.py
import sys
from pathlib import Path
if (args_count := len(sys.argv)) > 2:
print(f"One argument expected, got {args_count - 1}")
raise SystemExit(2)
elif args_count < 2:
print("You must specify the target directory")
raise SystemExit(2)
target_dir = Path(sys.argv[1])
if not target_dir.is_dir():
print("The target directory doesn't exist")
raise SystemExit(1)
for entry in target_dir.iterdir():
print(entry.name)
This program processes the arguments provided at the command line, which are automatically stored in the sys.argv
variable. The first item in sys.argv
is the program’s name. The second item should be the target directory.
The app should only accept one target directory, so the args_count
variable must be 2
at most. If the app gets more than one target directory, then you print an error message and raise a SystemExit
exception with an exit status of 2
. This indicates that the app exited after a failure.
The elif
branch checks whether the user has provided a target directory. If that’s not the case, then you print an error message to the user and exit the app, raising a SystemExit
exception.
After checking the content of sys.argv
, you create a pathlib.Path
object to store the path to your target directory. If this directory doesn’t exist, then you inform the user and exit the app using the SystemExit
exception again. This time, the exit status is 1
to signal that the app faced an error during its execution. Finally, the for
loop lists the directory content, one entry per line.
On a Unix-like system, such as Linux and macOS, you can run the following command to check how the script works:
$ python ls.py
You must specify the target directory
$ echo $?
2
$ python ls.py /home /etc
One argument expected, got 2
$ echo $?
2
$ python ls.py .
hello.py
birds.py
grades.py
greeting.py
fizzbuzz.py
ls.py
mean.py
stack.py
square.py
rainbow.py
$ echo $?
0
When you run the script with no argument, then you get a message telling you that you need to provide a target directory. When you use the echo
command to check the exit code, represented by $?
, you’ll see that it’s 2
. In the second example, you provide two target directories. Again, the app fails with an error message. The echo
command also returns 2
.
Finally, you run the script with the current working directory as an argument. In this case, you get the list of files that live in that directory. When you run echo
, you get an exit status of 0
, which signals that the app’s execution was successful.
KeyboardInterrupt
The KeyboardInterupt
exception is somewhat different from other exceptions. Python raises this exception when the user intentionally presses the Ctrl+C key combination during the execution of a program. This action interrupts a running program.
For example, say that you’re working on a piece of code that involves a loop. By error, the code falls into an infinite loop. In this situation, you need a quick way to terminate the code’s execution. That’s when the Ctrl+C key combination comes in handy.
Consider the following toy loop:
>>> while True:
... print("Hello")
...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Traceback (most recent call last):
...
KeyboardInterrupt
This loop is intentionally written to be infinite. In practice, you can have real loops that run into infinite iteration because of a logical error. When you encounter this issue, you can press the Ctrl+C keys to terminate the code’s execution. As a result, Python will raise a KeyboardInterrupt
exception.
Finally, it’s important to note that you shouldn’t catch this exception in your code because this may prevent the interpreter from exiting.
Exception Groups
In Python 3.11 and greater, you’ll have the ExceptionGroup
and BaseExceptionGroup
classes. You can use them when you need to raise multiple unrelated exceptions at the same time. The difference between these classes is that BaseExceptionGroup
extends BaseException
, while ExceptionGroup
extends Exception
.
Note: To learn more about exception groups, check out the Python 3.11 Preview: Task and Exception Groups tutorial.
You can reach for these exceptions when an asynchronous program has several concurrent tasks that could fail at the same time. But in general, you will raise an ExceptionGroup
sparingly.
You can handle and raise exception groups as needed. Here’s how to catch an exception group with the associated except*
syntax:
>>> try:
... raise ExceptionGroup(
... "several exceptions",
... [
... ValueError("invalid value"),
... TypeError("invalid type"),
... KeyError("missing key"),
... ],
... )
... except* ValueError:
... print("Handling ValueError")
... except* TypeError:
... print("Handling TypeError")
... except* KeyError:
... print("Handling KeyError")
...
Handling ValueError
Handling TypeError
Handling KeyError
The except*
syntax allows you to catch individual exceptions on an exception group. This way, you can handle individual exceptions in specific ways.
Conclusion
You’ve learned about Python’s built-in exceptions, which provide a quick and efficient way to handle errors and exceptional situations in your code. Now, you know the most commonly used built-in exceptions, when they appear, and how to use them in your exception-handling code. You also learned that you can raise these exceptions in your code.
In this tutorial, you’ve:
- Learned what errors and exceptions are in Python
- Understood how Python organizes the built-in exceptions in a class hierarchy
- Explored the most commonly used built-in exceptions
- Learned how to handle and raise built-in exceptions in your code
This knowledge will help you efficiently debug your code because each exception has a specific meaning and use case. You’ll also be able to effectively handle and raise built-in exceptions in your code, which is a great skill for a Python developer.
Get Your Code: Click here to download the free sample code that you’ll use to learn about Python’s built-in exceptions.
Take the Quiz: Test your knowledge with our interactive “Python's Built-in Exceptions: A Walkthrough With Examples” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python's Built-in Exceptions: A Walkthrough With ExamplesIn this quiz, you'll test your understanding of Python's built-in exceptions. With this knowledge, you'll be able to effectively identify and handle these exceptions when they appear. Additionally, you'll be more familiar with how to raise some of these exceptions in your code.