Decorators and context managers are advanced features in Python that enhance code functionality and manage resources effectively. Decorators allow you to modify or extend the behavior of functions or methods, while context managers help manage resources, such as file handling, with clean and concise syntax.
Decorators
A decorator is a function that takes another function as input and extends or modifies its behavior without changing its structure.
Anatomy of a Decorator:
Define a decorator function.
Accept a function as an argument.
Define a wrapper function inside the decorator.
Return the wrapper function.
Example:
def my_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# Output:
# Before the function call
# Hello!
# After the function call
Python provides several built-in decorators such as @staticmethod, @classmethod, and @property.
Example:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
circle = Circle(5)
print(circle.radius) # Output: 5
circle.radius = 10
print(circle.radius) # Output: 10
Context Managers
Context managers handle resource management tasks such as opening and closing files, acquiring and releasing locks, etc. The most common example is the with statement.
Example with with:
with open("example.txt", "r") as file:
content = file.read()
print(content)
# File is automatically closed after the block
Creating Custom Context Managers
Custom context managers can be created using the __enter__() and __exit__() methods in a class.
Example:
class MyContext:
def __enter__(self):
print("Entering the context")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting the context")
with MyContext() as context:
print("Inside the context")
# Output:
# Entering the context
# Inside the context
# Exiting the context
Contextlib Module
The contextlib module provides utilities for creating and working with context managers.
Example with contextlib:
from contextlib import contextmanager
@contextmanager
def my_context():
print("Entering the context")
yield
print("Exiting the context")
with my_context():
print("Inside the context")
# Output:
# Entering the context
# Inside the context
# Exiting the context
Combining Decorators and Context Managers
You can use both features together to create clean, efficient, and readable code.
Example:
from contextlib import contextmanager
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}.")
return result
return wrapper
@log_decorator
@contextmanager
def managed_resource():
print("Resource acquired")
yield
print("Resource released")
with managed_resource():
print("Using the resource")
# Output:
# Calling managed_resource...
# Resource acquired
# Using the resource
# Resource released
# Finished managed_resource.
Exercises
Exercise 1:
Write a decorator that logs the execution time of a function.
Create a custom context manager to log the entry and exit of a block.
Solution:
class LogContext:
def __enter__(self):
print("Entering the block")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting the block")
with LogContext():
print("Inside the block")
# Output:
# Entering the block
# Inside the block
# Exiting the block
Best Practices
Use decorators for reusable and modular functionality.
Prefer contextlib for simple context managers.
Test custom context managers and decorators for edge cases.
Keep wrapper functions in decorators lightweight.
Use meaningful names for decorators and context manager classes.
In the next chapter, we will explore concurrency and parallelism, focusing on multithreading, multiprocessing, and asynchronous programming in Python.