Understanding Python Decorators: Concepts, Use Cases, and Code Examples
Python decorators are special functions that wrap other functions or classes to add functionality without modifying their source code, and this guide explains their principles, common use cases such as logging, timing, caching, and permission checks, and provides ten practical code examples illustrating various decorator patterns.
In Python, a decorator is a special type of function that can add extra behavior to other functions or classes without modifying their source code. A decorator receives a function (or class) as input and returns a new function that typically executes additional operations before or after the original function.
The syntax is simple: placing @decorator_name above a function definition tells Python to apply that decorator to the function.
Common scenarios for using decorators include logging, performance testing, permission validation, result caching, and argument validation.
Example 1: Simple decorator that prints function call information
def my_decorator(func):
def wrapper():
print(f"{func.__name__} is called")
func()
return wrapper
@my_decorator
def say_hello():
print("Hello, World!")
say_hello()Example 2: Parameterized decorator for personalized messages
def message_decorator(message):
def decorator(func):
def wrapper():
print(message)
func()
return wrapper
return decorator
@message_decorator("Welcome!")
def greet():
print("Nice to meet you.")
greet()Example 3: Timing decorator that measures execution time
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time} seconds")
return result
return wrapper
@timer_decorator
def long_running_function(seconds):
time.sleep(seconds)
long_running_function(2)Example 4: Logging decorator that records function call details
import logging
logging.basicConfig(level=logging.INFO)
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
add(3, 5)Example 5: Permission check decorator that simplifies access control
def admin_required(func):
def wrapper(user):
if user['role'] != 'admin':
raise Exception("Permission denied.")
return func(user)
return wrapper
@admin_required
def delete_user(user):
print(f"Deleting user {user['username']}")
user = {'username': 'Alice', 'role': 'admin'}
delete_user(user)Example 6: Caching decorator using functools.lru_cache to avoid repeated calculations
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # first computation
print(fibonacci(10)) # retrieved from cacheExample 7: Type‑checking decorator that validates argument types automatically
def type_check_decorator(*arg_types):
def decorator(func):
def wrapper(*args):
if len(args) != len(arg_types):
raise TypeError("Argument count mismatch")
for a, t in zip(args, arg_types):
if not isinstance(a, t):
raise TypeError(f"Argument {a} is not of type {t}")
return func(*args)
return wrapper
return decorator
@type_check_decorator(int, int)
def add_numbers(a, b):
return a + b
print(add_numbers(2, 3))
# print(add_numbers('2', 3)) # would raise TypeErrorExample 8: Singleton decorator that ensures a class has only one instance
def singleton(cls):
_instance = {}
def get_instance(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return get_instance
@singleton
class MyClass:
def __init__(self, value):
self.value = value
obj1 = MyClass(10)
obj2 = MyClass(20)
print(obj1 is obj2) # TrueExample 9: Class decorator that modifies class attributes
class ClassDecorator:
def __init__(self, decorated_class):
self.decorated_class = decorated_class
def __call__(self, *args, **kwargs):
instance = self.decorated_class(*args, **kwargs)
instance.name = "Decorated"
return instance
@ClassDecorator
class MyClass:
def __init__(self):
self.name = "Original"
obj = MyClass()
print(obj.name) # DecoratedExample 10: Chained decorators demonstrating multiple layers of decoration
def bold_decorator(func):
def wrapper():
return f"
{func()}
"
return wrapper
def italic_decorator(func):
def wrapper():
return f"
{func()}
"
return wrapper
@bold_decorator
@italic_decorator
def greet():
return "Hello, World!"
print(greet()) #
Hello, World!Through these examples we see the versatility and power of decorators: they can simplify code structure, improve readability, and enhance maintainability, encouraging further creative applications of Python decorators.
Test 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.