Understanding Python Decorators: Concepts and Practical Examples
Python decorators are powerful constructs that let you augment functions without altering their definitions, and this guide explains their underlying principle, provides a basic template, and demonstrates ten practical decorator implementations—including timing, logging, access control, caching, singleton, retry, async, and property restrictions.
In Python, decorators are a powerful feature that allows users to add extra functionality to functions without modifying the original function definition. A decorator is essentially a function that takes another function as an argument and returns a new function, typically executing additional operations before or after the original function.
Basic decorator template:
def my_decorator(func):
"""
This is a decorator template
Parameters:
func: the function to be decorated
Returns:
wrapper: the wrapped function
"""
def wrapper(*args, **kwargs):
"""
Internal function that wraps the original call
Parameters:
*args: positional arguments for the original function
**kwargs: keyword arguments for the original function
"""
# Add decorator functionality here, e.g., logging, timing, etc.
print("Executing some operations before the function call...")
# Call the original function
result = func(*args, **kwargs)
# Additional decorator functionality after the call
print("Executing some operations after the function call...")
return result
# Return the wrapped function
return wrapper
@my_decorator
def example_function(x):
"""Example function"""
print(f"Function executed, received argument: {x}")
example_function("Hello, decorator!")1. Performance testing decorator
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} execution time: {end_time - start_time} seconds")
return result
return wrapper
@timing_decorator
def long_running_function(n):
sum = 0
for i in range(n):
sum += i
return sum
print(long_running_function(1000000))2. Logging decorator
def logging_decorator(func):
def wrapper(*args, **kwargs):
print(f"{func.__name__} called with args: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} finished, result: {result}")
return result
return wrapper
@logging_decorator
def add(a, b):
return a + b
print(add(3, 5))3. Permission check decorator
def admin_required(func):
def wrapper(*args, **kwargs):
user_role = "admin" # Assume this is obtained from somewhere
if user_role != "admin":
raise Exception("Admin privileges required")
return func(*args, **kwargs)
return wrapper
@admin_required
def delete_user(user_id):
print(f"Deleting user with ID {user_id}")
try:
delete_user(123)
except Exception as e:
print(e)4. Simple caching decorator
cache = {}
def cache_decorator(func):
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@cache_decorator
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))5. Class method decorator
class Counter:
count = 0
@classmethod
def increment(cls):
cls.count += 1
print(f"Current counter value: {cls.count}")
Counter.increment()
Counter.increment()6. Singleton pattern decorator
def singleton(cls):
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
@singleton
class MyClass:
pass
a = MyClass()
b = MyClass()
print(a is b) # Should output True7. Retry decorator (for network requests, etc.)
import time
def retry_on_failure(max_retries=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
retries = 0
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {retries+1} failed, reason: {e}")
retries += 1
time.sleep(delay)
raise Exception(f"Failed after {max_retries} attempts")
return wrapper
return decorator
@retry_on_failure(max_retries=3)
def might_fail(some_condition):
if some_condition:
raise ValueError("Operation failed")
print("Operation succeeded")
might_fail(True) # Will attempt 3 times8. Argument validation decorator
def validate_args(min_value, max_value):
def decorator(func):
def wrapper(value):
if not (min_value <= value <= max_value):
raise ValueError(f"Value must be between {min_value} and {max_value}")
return func(value)
return wrapper
return decorator
@validate_args(0, 100)
def process_value(value):
print(f"Processing value: {value}")
process_value(50) # Normal
# process_value(150) # Would raise an exception9. Asynchronous support decorator
import asyncio
async def async_decorator(func):
async def wrapper(*args, **kwargs):
print("Starting async operation...")
result = await func(*args, **kwargs)
print("Async operation completed")
return result
return wrapper
@async_decorator
async def fetch_data():
print("Fetching data...")
await asyncio.sleep(1) # Simulate async work
print("Data fetch complete")
return "data"
asyncio.run(fetch_data())10. Class property decorator (read‑only attribute)
def readonly_property(func):
attr_name = "_{}".format(func.__name__)
@property
def wrapper(self):
return getattr(self, attr_name)
@wrapper.setter
def wrapper(self, value):
raise AttributeError("Attribute is read‑only, cannot set")
setattr(wrapper, "__doc__", func.__doc__)
return wrapper
class MyClass:
@readonly_property
def value(self):
"""This property is read‑only"""
return 42
obj = MyClass()
print(obj.value) # Outputs 42
# obj.value = 100 # Would raise AttributeErrorTest Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.