Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • šŸ“ Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • šŸ“ Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • šŸ“ Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • šŸ“ Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • šŸ“ While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • šŸ“ For Each Assignments
    • šŸ“ All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • šŸ“ Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • šŸ“ Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • šŸ“ Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • šŸ“ Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • šŸ“ Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • šŸ“ Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • šŸ“ Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • šŸ“ Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • šŸ“ Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • šŸ“ Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • šŸ“ Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • šŸ“ Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • šŸ“ Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • šŸ“ Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • šŸ“ Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • šŸ“ Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • šŸ“ Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • šŸ“ Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • šŸ“ Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Context Managers

Python Context Managers - Complete Guide

Learn how to manage resources automatically and write cleaner code.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What are Context Managers?
  • Why Use Context Managers?
  • The with Statement
  • Built-in Context Managers
  • Creating Context Managers (Class)
  • Creating Context Managers (contextlib)
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What are context managers — a way to manage resources automatically
  • Why use them — clean, safe, and automatic resource management
  • The with statement — how to use context managers
  • Built-in context managers — files, locks, and more
  • Creating your own — two ways to build context managers
  • Real-world use — practical examples

What are Context Managers?

A context manager is a Python object that helps you manage resources like files, network connections, or database connections. It makes sure that resources are properly set up before you use them and properly cleaned up after you're done.

Think of it like a restaurant manager. When you sit at a table, the manager makes sure the table is clean and ready for you. When you leave, the manager makes sure the table is cleaned up for the next person. You don't have to worry about it.

In Python, context managers work with the with statement. You've probably used them before without realizing it.

šŸ’” Key concept: Context managers automatically handle setup and cleanup. They make sure resources are properly released even if something goes wrong.

Why Use Context Managers?

1

The Benefits of Context Managers

Context managers make your code safer and cleaner. Let's see why.

# Why Use Context Managers?

print("=" * 50)
print("WHY USE CONTEXT MANAGERS?")
print("=" * 50)

# ============================================================
# WITHOUT CONTEXT MANAGER — Manual and Error-Prone
# ============================================================

print("\nāŒ WITHOUT CONTEXT MANAGER:")

file = None
try:
    file = open("example.txt", "w")
    file.write("Hello, World!")
    file.close()  # Manual cleanup
except Exception as e:
    print(f"   Error: {e}")
finally:
    if file and not file.closed:
        file.close()  # Need to handle this too

print("   āŒ Lots of code just to handle a file")
print("   āŒ Easy to forget to close the file")
print("   āŒ Harder to read and maintain")


# ============================================================
# WITH CONTEXT MANAGER — Clean and Automatic
# ============================================================

print("\nāœ… WITH CONTEXT MANAGER:")

with open("example.txt", "w") as file:
    file.write("Hello, World!")
# File is automatically closed when the block ends

print("   āœ… Just 2 lines of code")
print("   āœ… File is automatically closed")
print("   āœ… Even if an error happens, the file is closed")
print("   āœ… Much cleaner and safer")


# ============================================================
# BENEFITS
# ============================================================

print("\n" + "-" * 30)
print("BENEFITS OF CONTEXT MANAGERS")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ BENEFIT             │ WHAT IT MEANS FOR YOU                    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Automatic cleanup   │ Resources are always properly released  │
│                     │                                            │
│ Exception safety    │ Even if errors happen, cleanup happens   │
│                     │                                            │
│ Less code           │ No more try/finally blocks              │
│                     │                                            │
│ Readable code       │ The with statement clearly shows         │
│                     │ the scope of the resource                 │
│                     │                                            │
│ Consistent behavior │ Same pattern for all resources           │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ Context managers make resource management automatic and safe.
""")

Benefits of context managers:

  • Automatic cleanup — resources are always released
  • Exception safety — cleanup happens even with errors
  • Less code — no manual try/finally blocks
  • Readable — the with statement makes the scope clear
  • Consistent — same pattern for all resources

Quick Check: What does a context manager handle automatically? (Answer: Setup and cleanup of resources)

The with Statement

2

How to Use the with Statement

The with statement is how you use a context manager. It's simple and easy to remember.

# The with Statement

print("=" * 50)
print("THE WITH STATEMENT")
print("=" * 50)

# ============================================================
# BASIC USAGE
# ============================================================

print("\n1. BASIC USAGE")

# Open a file and automatically close it
with open("test.txt", "w") as file:
    file.write("Hello, World!")
    print("   File written and closed automatically")

# Reading from a file
with open("test.txt", "r") as file:
    content = file.read()
    print(f"   File content: {content}")


# ============================================================
# MULTIPLE CONTEXT MANAGERS
# ============================================================

print("\n2. MULTIPLE CONTEXT MANAGERS")

# Copy from one file to another
with open("test.txt", "r") as source, open("copy.txt", "w") as dest:
    content = source.read()
    dest.write(content)
    print("   File copied successfully")

# Or on separate lines
with open("test.txt", "r") as source:
    with open("copy.txt", "w") as dest:
        content = source.read()
        dest.write(content)
        print("   File copied (nested)")


# ============================================================
# CONTEXT MANAGER WITH ASYNCHRONOUS CODE
# ============================================================

print("\n3. CONTEXT MANAGER WITH ASYNCIO (Async)")

import asyncio

async def read_file_async():
    # Async context manager (Python 3.7+)
    async with open("test.txt", "r") as file:
        content = file.read()
        return content

# async def run():
#     content = await read_file_async()
#     print(f"   Async read: {content}")
# 
# asyncio.run(run())

print("   āœ… async with works with async context managers")


# ============================================================
# NAMING THE CONTEXT MANAGER
# ============================================================

print("\n4. NAMING THE CONTEXT MANAGER")

# The variable after 'as' is the context manager's result
with open("test.txt", "r") as file:  # 'file' is the file object
    content = file.read()
    print(f"   Content: {content}")

# You can also use it without 'as'
with open("test.txt", "r"):  # Less common
    print("   File is open, but we don't have a reference")


# ============================================================
# WHAT HAPPENS BEHIND THE SCENES
# ============================================================

print("\n5. WHAT HAPPENS BEHIND THE SCENES")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ The with statement does this:                                      │
│                                                                     │
│ 1. Calls the context manager's __enter__() method                  │
│ 2. Executes the code inside the block                              │
│ 3. Calls the context manager's __exit__() method                  │
│    - Even if an exception happens!                                 │
│                                                                     │
│ This is why resources are always properly cleaned up.             │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

The with statement key points:

  • with ... as — the syntax for using context managers
  • Multiple managers — can use multiple in one line
  • Async with — async with for async context managers
  • Automatic — cleanup happens even with errors
  • Scope — the resource is only available inside the block

Quick Check: What does the 'as' keyword do in a with statement? (Answer: It gives you a reference to the context manager's result)

Built-in Context Managers

3

Context Managers Already in Python

Python has many built-in context managers that you can use right away.

# Built-in Context Managers

print("=" * 50)
print("BUILT-IN CONTEXT MANAGERS")
print("=" * 50)

import threading
import tempfile
import contextlib

# ============================================================
# 1. FILE OPERATIONS
# ============================================================

print("\n1. FILE OPERATIONS")

# Reading a file
with open("data.txt", "w") as f:
    f.write("Hello, World!")

with open("data.txt", "r") as f:
    content = f.read()
    print(f"   Content: {content}")


# ============================================================
# 2. THREAD LOCKS
# ============================================================

print("\n2. THREAD LOCKS")

lock = threading.Lock()

def safe_function():
    with lock:
        # Only one thread can run this at a time
        print("   Thread-safe operation")
    # Lock is automatically released

safe_function()


# ============================================================
# 3. TEMPORARY FILES
# ============================================================

print("\n3. TEMPORARY FILES")

with tempfile.NamedTemporaryFile(mode='w', delete=True) as temp:
    temp.write("Temporary data")
    temp.flush()
    print(f"   Temporary file: {temp.name}")
# File is automatically deleted when the block ends


# ============================================================
# 4. SUPPRESSING ERRORS (contextlib)
# ============================================================

print("\n4. SUPPRESSING ERRORS")

from contextlib import suppress

with suppress(FileNotFoundError):
    # This won't raise an error if the file doesn't exist
    with open("missing.txt", "r") as f:
        content = f.read()
print("   āŒ No error raised (FileNotFoundError was suppressed)")


# ============================================================
# 5. CHANGING DIRECTORY (contextlib)
# ============================================================

print("\n5. CHANGING DIRECTORY")

import os

print(f"   Current directory: {os.getcwd()}")

with contextlib.chdir(".."):  # Go up one directory
    print(f"   In with block: {os.getcwd()}")
# Directory is restored after the block

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


# ============================================================
# 6. REDIRECTING OUTPUT (contextlib)
# ============================================================

print("\n6. REDIRECTING OUTPUT")

import io

with io.StringIO() as buffer:
    with contextlib.redirect_stdout(buffer):
        print("This goes to the buffer, not the console")
    output = buffer.getvalue()
    print(f"   Captured output: {repr(output)}")


# ============================================================
# 7. TIMING CODE
# ============================================================

print("\n7. TIMING CODE")

import time

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

with Timer():
    time.sleep(0.5)
    print("   Operation completed")


# ============================================================
# SUMMARY
# ============================================================

print("\n" + "-" * 30)
print("BUILT-IN CONTEXT MANAGERS SUMMARY")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ CONTEXT MANAGER     │ WHAT IT DOES                                      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ open()              │ Opens and automatically closes files              │
│ threading.Lock      │ Acquires and releases locks                      │
│ tempfile            │ Creates and deletes temporary files               │
│ suppress()          │ Suppresses specified exceptions                   │
│ chdir()             │ Changes directory and restores it                 │
│ redirect_stdout()   │ Redirects output temporarily                      │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ Python has many built-in context managers. Learn them and use them!
""")

Built-in context managers key points:

  • open() — file operations
  • threading.Lock — thread safety
  • tempfile — temporary files
  • suppress() — ignore specific errors
  • redirect_stdout() — capture output

Quick Check: What context manager would you use to ignore a FileNotFoundError? (Answer: contextlib.suppress(FileNotFoundError))

Creating Context Managers (Class)

4

The __enter__ and __exit__ Methods

You can create your own context manager by defining a class with __enter__ and __exit__ methods.

# Creating Context Managers with Classes

print("=" * 50)
print("CREATING CONTEXT MANAGERS (CLASS)")
print("=" * 50)

import time

# ============================================================
# BASIC CONTEXT MANAGER
# ============================================================

print("\n1. BASIC CONTEXT MANAGER")

class FileOpener:
    """A context manager for opening files"""
    
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        """Called when entering the with block"""
        print(f"   Opening file: {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file  # This is what 'as' gets
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Called when exiting the with block"""
        print(f"   Closing file: {self.filename}")
        if self.file:
            self.file.close()
        # Return False to propagate exceptions, True to suppress them
        return False

# Using the context manager
with FileOpener("test.txt", "w") as file:
    file.write("Hello, World!")


# ============================================================
# TIMING CONTEXT MANAGER
# ============================================================

print("\n2. TIMING CONTEXT MANAGER")

class TimerContext:
    """Context manager that measures execution time"""
    
    def __enter__(self):
        self.start = time.perf_counter()
        print("   Timer started")
        return self
    
    def __exit__(self, *args):
        self.end = time.perf_counter()
        self.duration = self.end - self.start
        print(f"   Timer stopped: {self.duration:.4f}s")
        return False

with TimerContext() as timer:
    time.sleep(0.5)
    print("   Doing some work...")


# ============================================================
# CONTEXT MANAGER WITH EXCEPTION HANDLING
# ============================================================

print("\n3. CONTEXT MANAGER WITH EXCEPTION HANDLING")

class SafeDivider:
    """Context manager that handles division by zero"""
    
    def __enter__(self):
        print("   Entering division context")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ZeroDivisionError:
            print(f"   Caught division by zero: {exc_val}")
            return True  # Suppress the exception
        return False  # Let other exceptions propagate
    
    def divide(self, a, b):
        return a / b

with SafeDivider() as divider:
    # This would normally raise an error
    result = divider.divide(10, 0)
    print(f"   Result: {result}")
print("   Program continues normally")


# ============================================================
# CONTEXT MANAGER WITH RESOURCE MANAGEMENT
# ============================================================

print("\n4. CONTEXT MANAGER WITH RESOURCE MANAGEMENT")

class DatabaseConnection:
    """Simulate a database connection context manager"""
    
    def __init__(self, db_name):
        self.db_name = db_name
        self.connected = False
    
    def __enter__(self):
        print(f"   Connecting to {self.db_name}")
        self.connected = True
        return self
    
    def __exit__(self, *args):
        print(f"   Disconnecting from {self.db_name}")
        self.connected = False
        return False
    
    def query(self, sql):
        if not self.connected:
            raise RuntimeError("Not connected to database")
        return f"   Result of: {sql}"

with DatabaseConnection("users.db") as db:
    result = db.query("SELECT * FROM users")
    print(result)
print("   Database connection closed")


# ============================================================
# UNDERSTANDING __exit__ PARAMETERS
# ============================================================

print("\n5. UNDERSTANDING __exit__ PARAMETERS")

class ExceptionHandler:
    """Demonstrates the __exit__ parameters"""
    
    def __enter__(self):
        print("   Entering context")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"   exc_type: {exc_type}")
        print(f"   exc_val: {exc_val}")
        print(f"   exc_tb: {exc_tb}")
        return False

print("   No exception:")
with ExceptionHandler():
    pass

print("\n   With exception:")
try:
    with ExceptionHandler():
        raise ValueError("Something went wrong!")
except ValueError as e:
    print(f"   Caught exception: {e}")

Class-based context managers key points:

  • __enter__ — called when entering the with block
  • __exit__ — called when exiting, even with errors
  • Return value — __enter__ returns the resource
  • Exception handling — __exit__ gets exception details
  • Suppress exceptions — return True from __exit__

Quick Check: What two methods does a class need to be a context manager? (Answer: __enter__ and __exit__)

Creating Context Managers (contextlib)

5

The Easier Way with contextlib

The contextlib module gives you a simpler way to create context managers using the @contextmanager decorator.

# Creating Context Managers with contextlib

print("=" * 50)
print("CREATING CONTEXT MANAGERS (contextlib)")
print("=" * 50)

from contextlib import contextmanager
import time

# ============================================================
# BASIC CONTEXT MANAGER
# ============================================================

print("\n1. BASIC CONTEXT MANAGER")

@contextmanager
def open_file(filename, mode):
    """Context manager for files (using contextlib)"""
    print(f"   Opening file: {filename}")
    file = open(filename, mode)
    try:
        yield file  # This is the resource
    finally:
        print(f"   Closing file: {filename}")
        file.close()

with open_file("test.txt", "w") as file:
    file.write("Hello, World!")


# ============================================================
# TIMING CONTEXT MANAGER
# ============================================================

print("\n2. TIMING CONTEXT MANAGER")

@contextmanager
def timer(name="Operation"):
    """Context manager that measures execution time"""
    print(f"   ā±ļø Starting {name}...")
    start = time.perf_counter()
    try:
        yield
    finally:
        duration = time.perf_counter() - start
        print(f"   ā±ļø {name} took {duration:.4f}s")

with timer("Sleep"):
    time.sleep(0.5)
    print("   Work done")


# ============================================================
# CONTEXT MANAGER WITH RESOURCES
# ============================================================

print("\n3. CONTEXT MANAGER WITH RESOURCES")

@contextmanager
def database_connection(db_name):
    """Simulate a database connection"""
    print(f"   Connecting to {db_name}")
    # Setup
    connection = {"connected": True, "db": db_name}
    try:
        yield connection  # Resource
    finally:
        print(f"   Disconnecting from {db_name}")
        connection["connected"] = False

with database_connection("users.db") as db:
    print(f"   Connected: {db['connected']}")
    print(f"   Database: {db['db']}")


# ============================================================
# CONTEXT MANAGER WITH EXCEPTION HANDLING
# ============================================================

print("\n4. CONTEXT MANAGER WITH EXCEPTION HANDLING")

@contextmanager
def ignore_errors(error_types):
    """Context manager that ignores specified errors"""
    try:
        yield
    except error_types as e:
        print(f"   Ignored error: {e}")

with ignore_errors(ValueError):
    print("   Trying something that might fail")
    raise ValueError("This error will be ignored!")

print("   Program continues normally")


# ============================================================
# NESTED CONTEXT MANAGERS
# ============================================================

print("\n5. NESTED CONTEXT MANAGERS")

@contextmanager
def step(message):
    """Context manager for tracking steps"""
    print(f"   šŸ”¹ {message}")
    try:
        yield
    finally:
        print(f"   āœ… {message} done")

with step("Preparing data"):
    print("      Inside step 1")
    with step("Processing data"):
        print("         Inside step 2")
        time.sleep(0.1)


# ============================================================
# CONTEXT MANAGER THAT RETURNS A VALUE
# ============================================================

print("\n6. CONTEXT MANAGER THAT RETURNS A VALUE")

@contextmanager
def counter():
    """Context manager that returns a counter"""
    count = 0
    print("   Counter started")
    try:
        yield count
    finally:
        print(f"   Counter finished: {count}")

with counter() as c:
    c += 1
    c += 1
    print(f"   Inside: count = {c}")


# ============================================================
# CLASS VS CONTEXTLIB
# ============================================================

print("\n" + "-" * 30)
print("CLASS VS CONTEXTLIB")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ FEATURE             │ CLASS                    │ CONTEXTLIB                   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Code length         │ More code                │ Less code                    │
│                     │                          │                              │
│ Readability         │ Very clear               │ Very clear                   │
│                     │                          │                              │
│ Complexity          │ Good for complex logic   │ Great for simple logic       │
│                     │                          │                              │
│ Exception handling  │ Manual (__exit__)        │ Automatic (try/finally)     │
│                     │                          │                              │
│ Use case            │ Complex setup/cleanup    │ Simple setup/cleanup         │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ Use contextlib for simple context managers. Use classes for complex ones.
""")

contextlib key points:

  • @contextmanager — decorator that creates context managers
  • yield — the resource is yielded to the with block
  • try/finally — ensures cleanup happens
  • Less code — simpler than writing classes
  • Exception handling — automatic with try/except

Quick Check: What decorator from contextlib creates a context manager? (Answer: @contextmanager)

Real-World Example

6

Building a Database Connection Manager

# Real-World Example: Database Connection Manager

import time
import random
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Optional, List, Dict, Any

print("=" * 60)
print("DATABASE CONNECTION MANAGER")
print("=" * 60)

# ============================================================
# SIMULATED DATABASE
# ============================================================

class FakeDatabase:
    """Simulated database for demonstration"""
    
    def __init__(self):
        self.connected = False
        self.transactions = []
        self.data = {
            "users": [
                {"id": 1, "name": "Alice", "email": "alice@example.com"},
                {"id": 2, "name": "Bob", "email": "bob@example.com"},
                {"id": 3, "name": "Charlie", "email": "charlie@example.com"}
            ]
        }
    
    def connect(self):
        """Connect to the database"""
        if self.connected:
            raise RuntimeError("Already connected")
        self.connected = True
        print("   āœ… Connected to database")
    
    def disconnect(self):
        """Disconnect from the database"""
        if not self.connected:
            return
        self.connected = False
        print("   šŸ”Œ Disconnected from database")
    
    def query(self, sql: str) -> List[Dict[str, Any]]:
        """Execute a query"""
        if not self.connected:
            raise RuntimeError("Not connected to database")
        
        print(f"   šŸ“Š Query: {sql}")
        time.sleep(0.5)  # Simulate query time
        
        if sql.lower() == "select * from users":
            return self.data["users"]
        elif sql.lower() == "select * from users where active = true":
            return [u for u in self.data["users"] if u.get("active", True)]
        else:
            return []


# ============================================================
# CONTEXT MANAGER: CLASS APPROACH
# ============================================================

class DatabaseConnection:
    """Database connection context manager"""
    
    def __init__(self, db: FakeDatabase):
        self.db = db
    
    def __enter__(self):
        print("   šŸ”“ Acquiring database connection...")
        self.db.connect()
        return self.db
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("   šŸ”’ Releasing database connection...")
        if exc_type:
            print(f"   āš ļø Exception occurred: {exc_val}")
        self.db.disconnect()
        return False  # Don't suppress exceptions


# ============================================================
# CONTEXT MANAGER: CONTEXTLIB APPROACH
# ============================================================

@contextmanager
def db_transaction(db: FakeDatabase):
    """Database transaction context manager"""
    print("   šŸ”„ Starting transaction...")
    db.connect()
    
    try:
        yield db
        print("   āœ… Transaction committed")
    except Exception as e:
        print(f"   āŒ Transaction rolled back: {e}")
        raise
    finally:
        db.disconnect()


# ============================================================
# CONTEXT MANAGER: RETRY ON ERROR
# ============================================================

@contextmanager
def retry_on_error(max_retries: int = 3, delay: float = 1.0):
    """Context manager that retries on errors"""
    attempts = 0
    while attempts < max_retries:
        try:
            yield
            return  # Success, exit the context
        except Exception as e:
            attempts += 1
            print(f"   āš ļø Attempt {attempts} failed: {e}")
            if attempts < max_retries:
                print(f"   šŸ”„ Retrying in {delay}s...")
                time.sleep(delay)
            else:
                print(f"   āŒ All {max_retries} attempts failed")
                raise


# ============================================================
# DEMONSTRATION
# ============================================================

print("\n1. BASIC DATABASE CONNECTION")
db = FakeDatabase()

with DatabaseConnection(db) as conn:
    users = conn.query("SELECT * FROM users")
    for user in users:
        print(f"   šŸ‘¤ {user['name']} - {user['email']}")

print("\n2. DATABASE TRANSACTION")
try:
    with db_transaction(db) as conn:
        users = conn.query("SELECT * FROM users")
        print(f"   šŸ“Š Found {len(users)} users")
        
        # Simulate an error
        # raise ValueError("Something went wrong!")
except Exception as e:
    print(f"   āŒ Transaction failed: {e}")

print("\n3. RETRY ON ERROR")
error_count = 0

with retry_on_error(max_retries=3, delay=0.5):
    error_count += 1
    print(f"   šŸ”„ Attempt {error_count}")
    
    # Simulate random failure
    if random.random() < 0.7:  # 70% chance of failure
        raise ConnectionError("Database connection lost!")
    
    print("   āœ… Success!")

print("\n4. COMBINED: RETRY + TRANSACTION")
db2 = FakeDatabase()

try:
    with retry_on_error(max_retries=2, delay=0.5):
        with db_transaction(db2) as conn:
            users = conn.query("SELECT * FROM users")
            print(f"   šŸ“Š Successfully retrieved {len(users)} users")
except Exception as e:
    print(f"   āŒ Failed: {e}")

print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("āœ… Context managers make resource management automatic")
print("āœ… Database connections are properly opened and closed")
print("āœ… Transactions are committed or rolled back automatically")
print("āœ… Retry logic can be wrapped in a context manager")
print("āœ… Multiple context managers can be combined")
print("āœ… Class and contextlib approaches both have their uses")

Real-world example key points:

  • DatabaseConnection — class-based context manager
  • db_transaction — contextlib-based transaction manager
  • retry_on_error — context manager that retries on failure
  • Combined — context managers can be nested
  • Automatic cleanup — connections are always closed

Quick Check: What happens to a database connection when an error occurs in a context manager? (Answer: It's still properly closed because __exit__ is called even with errors)

Best Practices

7

Using Context Managers Effectively

# Best Practices for Context Managers

print("=" * 60)
print("BEST PRACTICES FOR CONTEXT MANAGERS")
print("=" * 60)

from contextlib import contextmanager

# ============================================================
# 1. USE CONTEXT MANAGERS FOR RESOURCES
# ============================================================

print("\n1. USE CONTEXT MANAGERS FOR RESOURCES")

# āœ… GOOD: Use context managers for files, connections, locks
with open("file.txt", "w") as f:
    f.write("data")

# āŒ BAD: Manual management (error-prone)
# f = open("file.txt", "w")
# f.write("data")
# f.close()  # Easy to forget!

print("   āœ… Context managers handle resources automatically")


# ============================================================
# 2. USE CONTEXTLIB FOR SIMPLE CONTEXT MANAGERS
# ============================================================

print("\n2. USE CONTEXTLIB FOR SIMPLE CONTEXT MANAGERS")

# āœ… GOOD: Use contextlib for simple logic
@contextmanager
def simple_timer():
    import time
    start = time.perf_counter()
    try:
        yield
    finally:
        print(f"   Took {time.perf_counter() - start:.4f}s")

# āŒ BAD: Writing a class for simple logic (overkill)
# class Timer:
#     def __enter__(self): ...
#     def __exit__(self, *args): ...

print("   āœ… contextlib is simpler for basic cases")


# ============================================================
# 3. USE CLASSES FOR COMPLEX CONTEXT MANAGERS
# ============================================================

print("\n3. USE CLASSES FOR COMPLEX CONTEXT MANAGERS")

# āœ… GOOD: Class for complex setup/cleanup
class ComplexResource:
    def __init__(self, config):
        self.config = config
        self.resource = None
    
    def __enter__(self):
        # Complex setup
        self.resource = self._setup()
        return self.resource
    
    def __exit__(self, *args):
        # Complex cleanup
        self._cleanup()
    
    def _setup(self): pass
    def _cleanup(self): pass

print("   āœ… Classes are better for complex logic")


# ============================================================
# 4. USE NESTED CONTEXT MANAGERS
# ============================================================

print("\n4. USE NESTED CONTEXT MANAGERS")

# āœ… GOOD: Nested for multiple resources
with open("input.txt", "r") as source:
    with open("output.txt", "w") as dest:
        data = source.read()
        dest.write(data)

# āœ… GOOD: Multiple in one line (for simple cases)
with open("input.txt", "r") as source, open("output.txt", "w") as dest:
    data = source.read()
    dest.write(data)

print("   āœ… Both ways work, choose what's readable")


# ============================================================
# 5. HANDLE EXCEPTIONS PROPERLY
# ============================================================

print("\n5. HANDLE EXCEPTIONS PROPERLY")

@contextmanager
def safe_resource():
    print("   Acquiring resource")
    try:
        yield
    except Exception as e:
        print(f"   Error during operation: {e}")
        raise  # Re-raise the exception
    finally:
        print("   Cleaning up resource")

print("   āœ… Always clean up, even on errors")


# ============================================================
# 6. DON'T SUPPRESS EXCEPTIONS UNLESS INTENTIONAL
# ============================================================

print("\n6. DON'T SUPPRESS EXCEPTIONS WITHOUT REASON")

# āŒ BAD: Suppressing all exceptions (dangerous!)
class BadManager:
    def __enter__(self): pass
    def __exit__(self, *args):
        return True  # Suppresses ALL exceptions!

# āœ… GOOD: Only suppress specific exceptions you want to ignore
from contextlib import suppress

with suppress(FileNotFoundError):
    open("missing.txt", "r")

print("   āœ… Only suppress exceptions you intend to ignore")


# ============================================================
# 7. SUMMARY
# ============================================================

print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ PRACTICE                    │ WHY IT MATTERS                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Use context managers for    │ Automatic resource management             │
│ resources                   │                                             │
│                             │                                             │
│ Use contextlib for simple   │ Less code, easier to read                 │
│ cases                       │                                             │
│                             │                                             │
│ Use classes for complex     │ Better organization for complex logic     │
│ cases                       │                                             │
│                             │                                             │
│ Nest context managers       │ Handle multiple resources safely          │
│                             │                                             │
│ Handle exceptions properly  │ Cleanup happens even with errors          │
│                             │                                             │
│ Don't suppress exceptions   │ Only ignore errors you mean to ignore     │
│ without reason              │                                             │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ Context managers make your code safer, cleaner, and more reliable.
""")

Best practices summary:

  • Use for resources — files, connections, locks
  • Use contextlib for simple — less code
  • Use classes for complex — better organization
  • Nest context managers — handle multiple resources
  • Handle exceptions — cleanup always happens
  • Don't suppress blindly — only ignore intentional errors

Quick Check: What's the best way to handle multiple resources? (Answer: Nest context managers or use multiple in one with statement)

Try It Yourself

Experiment with context managers in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
CONTEXT MANAGERS - PRACTICE
==================================================

1. BUILT-IN CONTEXT MANAGERS
File content: Hello, Context Managers!

2. CLASS-BASED CONTEXT MANAGER
šŸ–Øļø Printer ready
Printing: Hello, World!
Printing: This is a test
šŸ–Øļø Printer done

3. CONTEXTLIB CONTEXT MANAGER
šŸ“Œ Loading data
Data loaded
āœ… Loading data complete
šŸ“Œ Processing data
Data processed
āœ… Processing data complete

4. CONTEXT MANAGER THAT RETURNS A VALUE
Inside: 3
Final count: 3

5. NESTED CONTEXT MANAGERS
Two files created
Two files created (one line)
šŸ†

You've Got It!

You now understand context managers in Python. You know how to use the with statement, use built-in context managers, and create your own using both class and contextlib approaches.

Quick Quiz

Test what you've learned:

1. What statement is used to use a context manager?
2. What method is called when entering a with block?
3. What method is called when exiting a with block?
4. What decorator from contextlib creates a context manager?
5. What happens if an exception occurs inside a with block?

Frequently Asked Questions

What is a context manager in Python? ā–¼

A context manager is an object that manages resources by handling setup and cleanup automatically. It works with the with statement and ensures resources are properly released even if errors occur.

What's the difference between class and contextlib context managers? ā–¼

Class-based context managers use __enter__ and __exit__ methods. contextlib uses the @contextmanager decorator with yield. Classes are better for complex logic, while contextlib is simpler for basic cases.

Can I use multiple context managers at once? ā–¼

Yes! You can nest context managers or use multiple in one with statement. For example: with open('a') as f1, open('b') as f2: or nested with statements.

Does cleanup happen if an error occurs? ā–¼

Yes! This is the main benefit of context managers. The __exit__ method (or the finally block in contextlib) is always called, even if an exception occurs in the with block.

Can I suppress exceptions in a context manager? ā–¼

Yes. In a class-based context manager, return True from __exit__. In contextlib, use a try/except around the yield and don't re-raise the exception. However, only suppress exceptions you intentionally want to ignore.

What are some common built-in context managers? ā–¼

Common built-in context managers include: open() for files, threading.Lock, tempfile.NamedTemporaryFile, contextlib.suppress, contextlib.chdir, and contextlib.redirect_stdout.

Where to Go From Here

Now that you understand context managers in Python, check out these related topics:

Decorators

Learn about decorators — another way to wrap functions.

Learn More →

Generators

Learn about generators — the foundation of contextlib.

Learn More →

Async/Await

Learn about async context managers with async with.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Dataclasses Next: Property Decorator →