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 AttributeErrorSigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
