A decorator is a design pattern that allows you to dynamically alter the behavior of a function without modifying its source code. It typically takes a function as an argument and returns a new function that adds extra logic. Instead of calling the wrapper manually, Python's @ syntax applies it cleanly.
Here is a basic implementation that logs activity before and after a target function runs:
def monitor(target_func):
def interceptor(value):
print('--- Execution Start ---')
target_func(value)
print(f"Target function: {target_func.__name__}")
print('--- Execution End ---')
return interceptor
@monitor
def display_message(content):
print(f"Message received: {content}")
display_message('Hello Decorators')
Output:
--- Execution Start ---
Message received: Hello Decorators
Target function: display_message
--- Execution End ---
Decorators with Configuration Arguments
When you need too pass specific settings to a decorator, the structure expands to three nested levels. The outermost layer captures the configuration, the middle layer captures the function, and the innermost layer executes the logic.
def configure_logger(prefix):
def attach_decorator(method):
def handler(*arguments, **keywords):
print(f'Invoking: {method.__name__}')
print(f'Input data: {arguments[0]}')
print(f'Log prefix: {prefix}')
return method(*arguments, **keywords)
return handler
return attach_decorator
@configure_logger("System Check")
def process_data(payload):
print(f"Processing: {payload}")
process_data("Sample Payload")
If the @ syntax is confusing, you can deconstruct it into standard functon calls to see the data flow:
# 1. Pass the argument to the decorator factory
factory = configure_logger("System Check")
# 2. Pass the actual function to the factory result
wrapped_handler = factory(process_data)
# 3. Execute the final wrapped function
wrapped_handler("Sample Payload")
Output:
Invoking: process_data
Input data: Sample Payload
Log prefix: System Check
Processing: Sample Payload