Core Concepts
Decorators in Python leverage the @ symbol as syntactic sugar to modify function behavior without altering their source code. When applied, the decorator wraps the target function, intercepting its execution flow.
When implementing decorators using classes, the decorated function becomes an instance of that clas. This pattern enables more complex state management and configuration options.
Fundamental Implementation: Logging Decorator
def execution_logger(target_func):
def interceptor(*params, **options):
print(f'Starting execution of: {target_func.__name__}')
target_func(*params, **options)
print('Execution completed successfully')
return interceptor
@execution_logger
def calculate_sum(first_value, second_value):
print(f'{first_value} + {second_value} = {first_value + second_value}')
calculate_sum(150, 75)
Output:
Starting execution of: calculate_sum
150 + 75 = 225
Execution completed successfully
Parameterized Function Decorators
To create flexible decorators that accept configuration parameters, a nested closure approach is required:
def greeting_decorator(country_code):
def layer_wrapper(original_func):
def execution_wrapper(*arguments, **keyword_args):
greetings = {
"japan": "こんにちは",
"france": "Bonjour",
"germany": "Guten Tag"
}
if country_code in greetings:
print(greetings[country_code])
else:
print("Hello")
original_func(*arguments, **keyword_args)
return execution_wrapper
return layer_wrapper
@greeting_decorator("japan")
def japanese_greeting():
print("私は日本から来ました。")
@greeting_decorator("france")
def french_greeting():
print("Je viens de France.")
Class-Based Decorators Without Parameters
Class decorators implement the __call__ magic method to enable function-like behavior:
class activity_tracker:
def __init__(self, target_function):
self.function_ref = target_function
def __call__(self, *args, **kwargs):
print(f"[TRACKING]: Executing {self.function_ref.__name__}()")
return self.function_ref(*args, **kwargs)
@activity_tracker
def display_message(content):
print(f"Message: {content}")
display_message("System operational")
Configurable Class Decorators
Parameterized class decorators separate initialization from function decoration phases:
class configurable_logger:
def __init__(self, log_level='INFO'):
self.severity = log_level
def __call__(self, decorated_func):
def wrapper_function(*func_args, **func_kwargs):
print(f"[{self.severity}]: Invoking {decorated_func.__name__}()")
decorated_func(*func_args, **func_kwargs)
return wrapper_function
@configurable_logger(log_level='ERROR')
def system_operation(task):
print(f"Processing {task}")
system_operation("data backup")
Advanced Pattern: Partial Functions with Classes
Combining functools.partial with callable classes creates sophisticated decorators:
import time
import functools
class delayed_execution:
def __init__(self, delay_duration, target_func):
self.wait_period = delay_duration
self.target = target_func
def __call__(self, *call_args, **call_kwargs):
print(f'Delaying execution for {self.wait_period} seconds')
time.sleep(self.wait_period)
return self.target(*call_args, **call_kwargs)
def immediate_invoke(self, *args, **kwargs):
print('Executing without delay')
return self.target(*args, **kwargs)
def deferred_call(delay_seconds):
return functools.partial(delayed_execution, delay_seconds)
@deferred_call(delay_seconds=3)
def multiply_values(x, y):
return x * y
Singleton Pattern Implementation
active_instances = {}
def enforce_singleton(original_class):
def get_or_create_instance(*constructor_args, **constructor_kwargs):
class_identifier = original_class.__name__
if class_identifier not in active_instances:
active_instances[class_identifier] = original_class(*constructor_args, **constructor_kwargs)
return active_instances[class_identifier]
return get_or_create_instance
@enforce_singleton
class Configuration:
def __init__(self, settings_data):
self.settings = settings_data
Practical Applications
Function Result Caching
from functools import lru_cache
@lru_cache(maxsize=64)
def compute_fibonacci(position):
if position <= 1:
return position
return compute_fibonacci(position-1) + compute_fibonacci(position-2)
print(compute_fibonacci(50))
Performance Benchmarking
import time
def benchmark_execution(target_proc):
def timing_wrapper(*proc_args, **proc_kwargs):
start_timestamp = time.perf_counter()
result = target_proc(*proc_args, **proc_kwargs)
end_timestamp = time.perf_counter()
print(f'{target_proc.__name__} completed in {end_timestamp - start_timestamp:.4f} seconds')
return result
return timing_wrapper
@benchmark_execution
def intensive_computation():
time.sleep(1)
return "Task finished"
Input Validation
def numeric_validator(operation_func):
def validation_layer(first_operand, second_operand):
if not all(isinstance(val, (int, float)) for val in [first_operand, second_operand]):
raise TypeError("Operands must be numeric values")
return operation_func(first_operand, second_operand)
return validation_layer
@numeric_validator
def arithmetic_addition(left, right):
return left + right
Exception Management
def exception_handler(executable_func):
def safeguard_wrapper(*func_params, **func_options):
try:
return executable_func(*func_params, **func_options)
except ZeroDivisionError:
print("Mathematical error: Division by zero detected")
return None
except TypeError as type_error:
print(f"Type mismatch error: {type_error}")
return None
return safeguard_wrapper
@exception_handler
def safe_division(dividend, divisor):
return dividend / divisor
Access Control
def authorization_required(permission_level):
def access_validator(target_method):
def security_check(*method_args, **method_kwargs):
if current_user_privileges() < permission_level:
print("Insufficient permissions for this operation")
return
return target_method(*method_args, **method_kwargs)
return security_check
return access_validator
@authorization_required(permission_level=5)
def administrative_task():
print("Executing privileged operation")