<jupyter_start><jupyter_text>Context Managers and the with StatementThe with statement in Python is regarded as an obscure feature by some, But when you peek behind the scenes, you'll see that there's no magic involved, and it's actually a highly useful feature that can gelp you weite cleaner and more readable Python code. It helps simplify some common resource management patterns by abstracting their functionality and allowing them to be factore out and reused.A good way to see this feature used effectively is by looking at examples in the Python standard library. The build-in open() function provides us with an excellent use case:<jupyter_code>with open('hello.txt', 'w') as f:
    f.write('Hello, world')

# How to work 'with statement' internally
f = open('hello.txt', 'w')
try:
    f.write('Hello, world')
finally:
    f.close()<jupyter_output><empty_output><jupyter_text>Opening files using the with statement is generally recommended because it ensures that open file descriptors are closed automatically after program execution leaves the context of the with statement. The *with statement* can make code that deals with system resources more readable. It also helps you avoid bugs or leaks by making ut practically impossible to forget to clean up or release a resource when it's no longer needed Context managersIt's a simple 'protocol' (or interface) that your object needs to follow in order to support some statements like to *with statement*. Supporting *with* in your own objectsYou can provide the same functionality in your own classes and functions by implementing so-called *context managers*Basically, all you need to do is add __enter__ and __exit__ methods to an object if you want it to function as a context manager. Python will call theses two methods at the appropriate times in the resource management cycle.<jupyter_code>class ManagedFile:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

with ManagedFile('hello.txt') as f:
    f.write('hello, world\n')
    f.write('bye now')<jupyter_output><empty_output><jupyter_text>Python calls \_\_enter\_\_ when execution enters the context of the *with statement* and it's time to acquire the resource. When execution leaves the context again, Python calls \_\_exit\_\_ to free up the resource.Writing a class-based context manager isn't the only way to support the *with statement* in Python. The *contextlib* utility module in the standard library provides a few more abstractions built on top of the basic context manager protocol. This can make your life a litter easier if your cases match what's offered by contextlib. we are going to rewrite our ManagedFile context manager example with this technique.<jupyter_code>from contextlib import contextmanager

# This method becomes a generator
@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

with managed_file('hello.txt') as f:
    f.write('Hello, world\n')
    f.write('bye now')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06395529338714685, 'fraction_numerical': 0.0, 'mean_word_length': 4.37, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '135627', 'license': 'MIT', 'n_tokens_mistral': 832, 'n_tokens_neox': 808, 'n_words': 426, 'path': 'Patterns for Cleaner Python/With-statement.ipynb', 'repo_name': 'jsparraq/python-tricks', 'size': '3221'}