{}const=>[]async()letfn</>var
Development

Context managers in Python: with, enter and exit

We analyze the with operator, enter and exit methods, create our own context managers and study practical examples of using them to work safely with resources.

К

Kodik

Author

6 min read

Imagine the situation: you open a file, work with it, and then forget to close it. Or you open a connection to a database, and suddenly an exception occurs that interrupts the execution of the code before you have time to free up resources. Sound familiar? Context managers in Python solve this problem elegantly and reliably.

Why do we need contextual managers?

Context managers provide automatic resource management. They ensure that a certain code will be executed before and after working with the resource, regardless of whether an error occurred or not.

A classic example without a context manager:

file = open('data.txt', 'r')
try:
    content = file.read()
    # Working with content
finally:
    file.close()  # Don't forget to close!

With the context manager, everything is much simpler:

with open('data.txt', 'r') as file:
    content = file.read()
    # The file will automatically close after exiting the block

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

How does the with construction work?

When Python meets the with operator, the following happens:

  1. The __enter__() method of the object is called

  2. The return value __enter__() is assigned to the variable after as (if specified)

  3. The code is executed inside the with block

  4. After the block is completed (or when an exception occurs), the __exit () method is called

This ensures that the resources are always cleaned up, even if an error occurs inside the block.

Creating your own contextual manager

To create your own context manager, you need to implement two special methods: __enter__ and __exit__.

A simple example: database connection manager

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None
    
    def __enter__(self):
        print(f"Opening connection to {self.db_name}")
        self.connection = f"Connection to {self.db_name}"
        return self.connection
    
    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Closing connection to {self.db_name}")
        if exc_type is not None:
            print(f"An error occurred: {exc_type.__name__}: {exc_value}")
        return False  # The exception will be passed on

# Use
with DatabaseConnection("users_db") as conn:
    print(f"We work with: {conn}")
    # Here is your code for working with the database

Conclusion:

Открываем соединение с users_db
Работаем с: Connection to users_db
Закрываем соединение с users_db

Method __enter ()

The __enter__() method is called when entering the context. It can:

  • Initialize resources

  • Establish connections

  • Capture locks

  • Return the object for work (which will be available through as)

def __enter__(self):
    # Preparation of resources
    self.resource = self.acquire_resource()
    return self.resource  # This value will be assigned to the variable after 'as'

Important: the method can return any object, including self. If you do not need a return value, you can simply use with without as.

Method __exit__(exc_type, exc_value, traceback)

The __exit__() method is called when exiting the context and takes three arguments:

  • exc_type — exception type (or None if there was no exception)

  • exc_value — exception object

  • traceback — traceback object for exception

The return value determines whether the exception will be suppressed:

  • True — the exception is suppressed (not forwarded further)

  • False or None — the exception is passed on

Example with exception handling

class ErrorHandler:
    def __init__(self, suppress_errors=False):
        self.suppress_errors = suppress_errors
    
    def __enter__(self):
        print("Starting execution")
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            print(f"Caught exception: {exc_type.__name__}")
            if self.suppress_errors:
                print("Exception suppressed")
                return True  # Suppress exception
        print("Completing")
        return False  # Throwing an exception

# The exception will be suppressed
with ErrorHandler(suppress_errors=True):
    print("Executing code")
    raise ValueError("Something went wrong!")
    print("This line will not be executed")

print("The program continues to work")

Practical examples of use

Code execution timer

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, *args):
        self.end = time.time()
        self.elapsed = self.end - self.start
        print(f"Execution time: {self.elapsed:.4f} seconds")

with Timer():
    # Measuring execution time
    total = sum(range(1000000))

Temporary directory change

import os

class ChangeDirectory:
    def __init__(self, new_path):
        self.new_path = new_path
        self.saved_path = None
    
    def __enter__(self):
        self.saved_path = os.getcwd()
        os.chdir(self.new_path)
        return self
    
    def __exit__(self, *args):
        os.chdir(self.saved_path)

with ChangeDirectory('/tmp'):
    print(f"Current directory: {os.getcwd()}")
    # Working in /tmp

print(f"Returned to: {os.getcwd()}")

Transaction management

class Transaction:
    def __init__(self, connection):
        self.connection = connection
    
    def __enter__(self):
        self.connection.begin()
        return self.connection
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            self.connection.commit()
            print("Transaction confirmed")
        else:
            self.connection.rollback()
            print("Transaction rolled back")
        return False

# Use
# with Transaction(db_connection) as conn:
#     conn.execute("INSERT INTO users VALUES (...)")
# # If an error occurs, the changes will be rolled back

Contextlib module — context managers without classes

Python provides the contextlib module, which makes it easier to create context managers using the @contextmanager decorator:

from contextlib import contextmanager

@contextmanager
def managed_file(filename, mode):
    print(f"Opening file {filename}")
    file = open(filename, mode)
    try:
        yield file  # Everything before yield is __enter__
    finally:
        print(f"Closing file {filename}")
        file.close()  # Everything after yield is __exit__

with managed_file('test.txt', 'w') as f:
    f.write('Hello, world!')

The yield keyword separates the logic: the code before it is executed when entering the context, the code after it is executed when exiting.

Suppressing exceptions with contextlib.suppress

from contextlib import suppress

# Ignore FileNotFoundError
with suppress(FileNotFoundError):
    os.remove('non-existent_file.txt')

print("The program continues to work")

Multiple Context Managers

Python allows you to use multiple context managers in one with statement:

with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    content = infile.read()
    outfile.write(content.upper())

This is equivalent to nested blocks:

with open('input.txt', 'r') as infile:
    with open('output.txt', 'w') as outfile:
        content = infile.read()
        outfile.write(content.upper())

When to use contextual managers?

Context managers are ideal for situations where you need a paired "setup/clean" operation:

  • File management

  • Managing connections to the database

  • Locks in multithreaded applications

  • Temporary change in the system state

  • Transaction management

  • Measurement of execution time

  • Logging in and out of code blocks

Tips and best practices

Always free resources in __exit__(). Even if an exception occurs, the __exit__() method will be called, so it must correctly clear the resources.

Use contextlib for simple cases. If you don't need all the power of the class, the @contextmanager decorator will greatly simplify the code.

Be careful with suppressing exceptions. Return True from __exit__() only when you are sure that the exception has been handled correctly.

Context managers can be reused. The same context manager object can be used multiple times if it correctly reinitializes the state.

Test behavior in case of exceptions. Make sure that your context manager works correctly both in normal mode and when errors occur.

Context managers make your code cleaner, safer, and more reliable. They eliminate the need to remember to manually clean up resources and ensure that everything is done correctly, even if something goes wrong. This is one example of how Python helps developers write better code with less effort.

Appendix Code offers structured courses for beginner developers in Python, JavaScript, HTML, CSS, and other technologies. The training is structured so that you can gradually master the material from simple to complex.

Join our Telegram channel, where you will find community support, ask questions, and get help learning programming. Learning together is easier and more interesting!

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card