avatarAlexandru-Ioan Plesoiu

Summary

The provided content discusses the use of contextlib.suppress() in Python as a more readable and concise alternative to try-except blocks for handling exceptions.

Abstract

The article introduces Python's contextlib.suppress() function as an efficient tool for exception handling. It explains how contextlib.suppress() can be used to handle exceptions without the need for a pass statement in an except block, thus making the code cleaner and easier to read. The article compares the traditional try-except method with the contextlib.suppress() approach, providing examples to illustrate the differences. It also addresses performance considerations, showing through timing tests that while the performance difference is negligible in most cases, contextlib.suppress() may introduce a significant overhead when used in high-performance systems with a large number of iterations. The article concludes by recommending contextlib.suppress() for general use but advises against it in systems requiring high performance.

Opinions

  • The author suggests that using contextlib.suppress() results in more readable code compared to the traditional try-except pattern.
  • The article implies that ignoring exceptions using pass in except blocks is common practice among Python developers, especially when the exception handling is deferred or deemed unnecessary.
  • There is an opinion that contextlib.suppress() is particularly useful when dealing with multiple exceptions that need to be ignored, as it allows for a more compact representation without multiple try-except blocks.
  • The author emphasizes that while contextlib.suppress() is generally a good choice for its readability benefits, it may not be suitable for high-performance scenarios due to potential performance penalties.
  • The author encourages Python developers to follow their work for more advanced guides and insights into Python's features, tips, and tricks.

From 50 Python features, tips & tricks that you don’t know (click)

How to use suppress() to handle Exceptions like a PRO — Python

This feature makes your code more readable and uses less lines of code.

In Python, when you use a try-except block and write pass in the except block, it is called an exception handling with a null operation. The pass keyword is a placeholder statement in Python that does nothing. At some point we all did that, because this approach is useful when you want to catch an exception and handle it later or when you want to ignore the exception entirely. By using pass in the except block, you are essentially telling the interpreter to move on to the next line of code without taking any action in response to the exception.

import os

filename = "example.txt"

try:
    os.remove(filename)
except FileNotFoundError:
    pass

This is how we normally handle this situation, but we can make this code more readable and short using contextlib.suppress()

contextlib.suppress()

contextlib.suppress is a context manager in Python that allows you to suppress specific exceptions from being raised within a block of code. It's part of the contextlib module, which provides utilities for working with context managers and the with statement. Essentially, contextlib.suppress helps you write cleaner and more concise error handling code when you don't need to do anything if a particular exception occurs.

import os
import contextlib

filename = "example.txt"

with contextlib.suppress(FileNotFoundError):
    os.remove(filename)

Now, let’s compare both versions of code

import os
import contextlib

filename = "example.txt"

# Without contextlib.suppress
try:
    os.remove(filename)
except FileNotFoundError:
    pass

# With contextlib.suppress
with contextlib.suppress(FileNotFoundError):
    os.remove(filename)

Both versions of the code above achieve the same result: we try to remove a file and do nothing if the file is not found. The version using contextlib.suppress is more concise and arguably more readable.

Real world scenario

Let’s suppose that we want to delete all temporary files within a directory that has a certain extension.

import os
import contextlib
import logging

def clean_temp_files(temp_directory, extensions_to_remove):
    """Remove temporary files with specific extensions from a directory."""
    for root, _, files in os.walk(temp_directory):
        for file in files:
            if any(file.endswith(ext) for ext in extensions_to_remove):
                file_path = os.path.join(root, file)
                with contextlib.suppress(FileNotFoundError, PermissionError):
                    os.remove(file_path)
                    logging.info(f"Deleted temporary file: {file_path}")

temp_directory = "/tmp"
extensions_to_remove = [".tmp", ".log"]

clean_temp_files(temp_directory, extensions_to_remove)

In this code example, we define a function clean_temp_files that takes a directory path and a list of file extensions to remove. It walks through the directory, checks each file's extension, and attempts to delete the file if its extension matches one in the list. We use contextlib.suppress to ignore FileNotFoundError and PermissionError exceptions, which might occur if a file is not found or if we don't have permission to delete it.

This way, our code can continue to delete other files even if some files can’t be deleted, and we don’t need to clutter the code with multiple try-except blocks.

Performance overhead

In terms of performance, the difference between the two methods will be negligible in most cases, and the choice should be driven by code readability, maintainability, and your specific use case. But, you should avoid using this when your code is actively being executed by a program or system.

Let’s try to timeit to see how both would perform.

import contextlib
import os
import timeit

filename = "nonexistent_file.txt"

def try_except_method():
    try:
        os.remove(filename)
    except FileNotFoundError:
        pass

def suppress_method():
    with contextlib.suppress(FileNotFoundError):
        os.remove(filename)

# Measure the time it takes to execute each method
try_except_time = timeit.timeit(try_except_method, number=1_000)
suppress_time = timeit.timeit(suppress_method, number=1_000)

print(f"try-except time: {try_except_time:.6f} seconds")
print(f"contextlib.suppress time: {suppress_time:.6f} seconds")

Result for 10 iterations:

try-except time: 0.000045 seconds
contextlib.suppress time: 0.000049 seconds

Result for 1.000 iterations:

try-except time: 0.002316 seconds
contextlib.suppress time: 0.003152 seconds

Result for 10.000 iterations:

try-except time: 0.025602 seconds
contextlib.suppress time: 0.029260 seconds

Result for 100.000 iterations:

try-except time: 0.215278 seconds
contextlib.suppress time: 0.273794 seconds

Result for 10.000.000 iterations:

try-except time: 20.464040 seconds
contextlib.suppress time: 28.150847 seconds

As you may see, for fewer iterations the difference between the two methods is negligible, but when we increase iterations, contextlib.suppress is much slower. This does not mean you should not use the suppress approach, this method should be avoided when you are dealing with lots of try-except blocks that are actively run.

As a conclusion, for most use cases it’s just fine to use contextlib.suppress, but when you are dealing with very high performant systems, it should be avoided.

Thank you for reading this article. If you want to dive deep into Python, follow me. I will write more advanced guides about Python soon including a series of article about features that i wrote about in 50 Python features, tips & tricks that you don’t know.

Python
Data Science
Programming
Software Engineering
Software Development
Recommended from ReadMedium