1. Decorator¶
A technique that allows adding functionality before or after a function to enhance its usability.
- Utilizes Closure functions.
- Reference: https://www.python.org/dev/peps/pep-0318/
Example: In the code below, the @decorator_func
part is the decorator.
@decorator_func def function(): print("what is decorator?")
def logger_login():
print('Kim has logged in')
logger_login()
Kim has logged in
# Add timestamps before and after the log.
import datetime
def logger_login():
print(datetime.datetime.now())
print('Kim has logged in')
print(datetime.datetime.now())
logger_login()
2024-12-30 20:28:26.227787 Kim has logged in 2024-12-30 20:28:26.227821
2. When to Use a Decorator?¶
When you want to add the same functionality to similar functions, like logger_logout
and logger_login
.
# When you want to add timestamps to similar functions like this:
def logger_logout():
print('Kim has logged out')
Kim has logged out
3. How to Define a Decorator¶
def decorator_name(target_function): def wrapper(): # Code to execute before the target function target_function() # Execute the target function # Code to execute after the target function Return the wrapper as a closure function
# Defining a decorator
# datetime_decorator is the name of the decorator, and func is the function to be wrapped
def datetime_decorator(func):
# A function that wraps the target function
def wrapper():
# Code to execute before the target function
print(f'start time: {datetime.datetime.now()}')
# Target function
func()
# Code to execute after the target function
print(f'end time: {datetime.datetime.now()}')
# Return as a closure function
return wrapper
# Using the decorator
@datetime_decorator
def logger_login():
print ("Kim has logged in")
logger_login()
@datetime_decorator
def logger_logout():
print ("Kim has logged out")
logger_logout()
start time: 2024-12-30 20:36:28.009713 Kim has logged in end time: 2024-12-30 20:36:28.009748 start time: 2024-12-30 20:36:28.009776 Kim has logged out end time: 2024-12-30 20:36:28.009781
4. Comparison with Nested Functions and Closure Functions¶
# Define a Decorator
def outer_func(function):
def inner_func():
print('decoration added')
function()
return inner_func
4.1. Nasted function and Closure function¶
def log_func():
print('logging')
decorated_func = outer_func(log_func)
decorated_func()
decoration added logging
4.2. Decorator¶
@outer_func
def log_func():
print('logging')
log_func()
decoration added logging
5. Applying a Decorator to a Function with Parameters¶
The nested function should have the same parameters as the function being decorated.
Add validation functionality to the following divide
function:
def divide(digit1, digit2): print(digit1 / digit2)
# Define a decorator
def validation(function):
def inner_func(digit1, digit2):
# Simple validation for division
if digit2 == 0:
print('cannot be divided by zero')
return
function(digit1, digit2)
return inner_func
# Use the decorator (validation check)
@validation
def divide(digit1, digit2):
print(digit1 / digit2)
divide(4, 2)
divide(9, 0)
2.0 cannot be divided by zero
def type_checker(func):
def inner_func(digit1, digit2):
val = True
if (type(digit1)!=int):
val = False
print("1st param is not 'int' tpye.")
if (type(digit2)!=int):
val = False
print("2nd param is not 'int' tpye.")
if not val:
return
return func(digit1, digit2)
return inner_func
@type_checker
def multiply_int(digit1, digit2):
return digit1 * digit2
print(multiply_int(3,2))
print(multiply_int(0.3, 0.4))
6 1st param is not 'int' tpye. 2nd param is not 'int' tpye. None 2nd param is not 'int' tpye. None
6. Creating a Universal Decorator Applicable to Any Function¶
Parameters can ultimately be represented as (args, **kwargs).
If the inner function of the decorator is written with (
args, **kwargs), the decorator can be applied to any function.
# Define a decorator
def general_decorator(function):
def wrapper(*args, **kwargs):
print('function is decorated')
return function(*args, **kwargs)
return wrapper
# Use the decorator
@general_decorator
def calc_square(digit):
return digit * digit
# Use the decorator
@general_decorator
def calc_plus(digit1, digit2):
return digit1 + digit2
# Use the decorator
@general_decorator
def calc_quad(digit1, digit2, digit3, digit4):
return digit1 * digit2 * digit3 * digit4
# Call the methods with appropriate arguments
print (calc_square(2))
print (calc_plus(2, 3))
print (calc_quad(2, 3, 4, 5))
function is decorated 4 function is decorated 5 function is decorated 120
7. Assigning Multiple Decorators to a Function¶
Multiple decorators can be assigned to a function (simply write multiple @decorator
lines).
Decorators are executed in the order they are listed.
# Define multiple decorators
def decorator1(func):
def wrapper():
print('Hello!')
func()
return wrapper
def decorator2(func):
def wrapper():
print('My name is WoongKeol Kim!')
func()
return wrapper
# Apply multiple decorators to a function at once
@decorator1
@decorator2
def Introduce():
print('I am SW developer!')
Introduce()
Hello! My name is WoongKeol Kim! I am SW developer!
def mark_bold(func):
def wrapper(*args, **kwargs):
return f'<b>{func(*args, **kwargs)}</b>'
return wrapper
def mark_italic(func):
def wrapper(*args, **kwargs):
return f'<i>{func(*args, **kwargs)}</i>'
return wrapper
@mark_bold
@mark_italic
def create_html_markup(content):
return content
print (create_html_markup('Hi'))
@mark_bold
def create_html_markup(content):
return content
print (create_html_markup('Hi'))
@mark_italic
def create_html_markup(content):
return content
print (create_html_markup('Hi'))
<b><i>Hi</i></b> <b>Hi</b> <i>Hi</i>
%%html
<b><i>Hi</i></b>
<b>Hi</b>
<i>Hi</i>
8. Using Decorators with Class Methods¶
Decorators can be applied to methods within a class.
- When applying a decorator to a class method, the
self
parameter must be included as the first argument in both the method and the decorator's inner function.
# Define a decorator
def h1_tag(function):
# The inner function must include 'self' as the first parameter to apply the decorator to a class method
def func_wrapper(self, *args, **kwargs):
# Add <h1> tags to the return value of the target method
return "<h1>{0}</h1>".format(function(self, *args, **kwargs))
return func_wrapper
# Declare a class and apply a decorator to one of its methods
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
# Use the h1_tag decorator to wrap the return value of the get_name method with <h1> tags
@h1_tag
def get_name(self):
return self.first_name + ' ' + self.last_name
# Test the decorator by creating an instance of the Person class and calling the decorated method
davelee = Person('WoongKeol', 'Kim')
print(davelee.get_name())
<h1>WoongKeol Kim</h1>
9. Defining a Decorator with Parameters¶
It is possible to add parameters to a decorator.
# Add another layer of nested functions to create the decorator
def decorator1(num):
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
print(f'decorator1 {num}')
return func(*args, **kwargs)
return inner_wrapper
return outer_wrapper
def print_hello():
print('hello')
# With this structure, the decorator can be called as follows:
print_hello2 = decorator1(1)(print_hello)
print_hello2()
decorator1 1 hello
# This can be expressed as a decorator as follows:
@decorator1(1)
def print_hello():
print('hello')
print_hello()
decorator1 1 hello
# Add another layer of nested functions to create the decorator
def mark_html(mark):
def outer_wrapper(func):
def innter_wrapper(*args, **kwargs):
return f'<{mark}>{func(*args, **kwargs)}</{mark}>'
return innter_wrapper
return outer_wrapper
def print_hello(content):
return content
@mark_html('h1')
@mark_html('b')
def print_title(content):
return content
print_title('Hello! I am WoongKeol Kim')
'<h1><b>Hello! I am WoongKeol Kim</b></h1>'
%%html
<h1><b>Hello! I am WoongKeol Kim</b></h1>