Python's Built-In Exceptions: A Walkthrough With Examples

Python's Built-in Exceptions: A Walkthrough With Examples

by Leodanis Pozo Ramos May 15, 2024 intermediate python

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, tryexcept blocks, and the raise statement.

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:

Python
>>> 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.

To fix the error, you need to localize the affected code and correct the syntax. This action will remove the error:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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 tryexcept 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.

In Python, you can handle exceptions using the tryexcept 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:

Python
>>> 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 tryexcept block to prevent the breakdown:

Python
>>> 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.

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:

Python
>>> 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:

Python
>>> 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:

  1. Base class exceptions
  2. Concrete exceptions

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:

Python
>>> 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.

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
>>> # 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.

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:

Python
>>> 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:

Python
>>> 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.

Here’s an example where Python raises a TabError exception:

Python 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.

To try out the example, go ahead and run the file from the command line:

Shell
$ 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.

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:

Python
>>> 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.

Because ModuleNotFoundError is a subclass of ImportError, when you explicitly use the latter in a tryexcept 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:

  1. An import module statement fails to load a module for a reason not covered by ModuleNotFoundError.
  2. A from module import name statement fails to find name in the target module.

Now go ahead and run the following import statement to see the ImportError exception in action:

Python
>>> 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.

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:

Python
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:

Python
>>> 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.

If you can’t control the range of indices, then you can catch the exception in a tryexcept 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:

Python 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 tryexcept 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:

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:

Python
>>> 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.

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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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.

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.

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.

Consider the following toy function as an example of a recursive function:

Python
>>> 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:

Python
>>> 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.

You can use the sys.getrecursionlimit() function to check your current recursion depth:

Python
>>> 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:

Python
>>> 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:

  1. .__iter__() is called to initialize the iterator. It must return an iterator object.
  2. .__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.

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:

Python 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:

Python
>>> 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.

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:

Python 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:

Shell
$ 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:

Shell
$ 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:

Python
>>> 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:

Python
>>> 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 tryexcept 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:

Python
>>> 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.

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:

Python 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.

Here’s an example of how you can derive concrete birds from the above class:

Python 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:

Python
>>> 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.

The assert statement has the following syntax:

Python
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.

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:

Python 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:

Python 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:

Shell
$ 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:

Python 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:

Shell
$ 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.

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:

Python
>>> 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:

Python 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:

Shell
$ 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:

Python
>>> 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.

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:

Python
>>> 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.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics: intermediate python