Decorators are one of the most helpful and powerful tools of Python
These are used to modify the behavior of the function
Decorators provide the flexibility to wrap another function to expand the working of wrapped function, without permanently modifying it
In Decorators, functions are passed as an argument into another function and then called inside the wrapper function
def display(msg):
print(msg)
display("Hi")
func2 = display
func2("Hi")
Hi Hi
An inner function in Python can be returned from the outer function
def greet():
def greeting_at_dawn():
print("Good morning!")
return greeting_at_dawn
salute = greet()
salute()
Good morning!
A function that receives a function argument is known as a higher order function
def greet_some(func):
print("Good morning!",end=' ')
func()
def say_name():
print("Chetan")
greet_some(say_name)
Good morning! Chetan
A simple decorator function starts with a function definition, the decorator function, and then a nested function within the other wrapper function.
# example
#higher order fucntion or decorator,
# accepts func i.e. a method as argument
# returns a method in the return statement
def increase_number(func):
def increase_by_one(): #wrapper function
print("incrementing number by 1 ...")
#decorated get_number is assigned to another variable
number_plus_one = func()+1
return number_plus_one #new variable is returned
return increase_by_one #wrapper is returned
# inner function
def get_number():
return 5
#implementing decorator
get_new_number = increase_number(get_number)
print(get_new_number())
incrementing number by 1 ... 6
A simple decorator function is easily identified when it begins with the @ prefix, coupled with the decorated function underneath. We can edit our example to look like this.
def increase_number(func):
def increase_by_one():
print("incrementing by 1...")
number_plus_one = func() + 1
return number_plus_one
return increase_by_one
@increase_number #decorator increase_number
#extends the functionality of get_number()
def get_number():
return 5
print(get_number())
incrementing by 1... 6
There are cases where you may need to pass parameters to a decorator. The way around this is to pass parameters to the wrapper function, which are then passed down to the decorator
# Example:
# decorator definition
def multiply_numbers(func):
#wrapper function
def multiply_two_numbers(num1, num2):
print(f"we're multiplying two number {num1} and {num2}")
return func(num1,num2)
return multiply_two_numbers
# decorator multiply_numbers
@multiply_numbers
# inner function arguments/decorated function arguments
# are passed to wrapper through the decorator
def multiply_two_given_numbers(num1,num2):
return f'{num1} * {num2} = {num1 * num2}'
print(multiply_two_given_numbers(2,5))
we're multiplying two number 2 and 5 2 * 5 = 10
*args
and **kwargs
to decorated function¶(*args)
or key word arguments (**kwargs)
can be passed unto the decorated function.*args
allows the collection of all positional arguments, while **kwargs
is for all the keyword arguments required and needed during the function call*args
forms an iterable of positional arguments as a tuple, while the **kwargs
forms a dictionay of keyword arguments# Example:
def decorator_func(decorated_func):
def wrapper_func(*args, **kwargs):
print(f"there are {len(args)} positional arguments and {len(kwargs)} keyword arguments")
return decorated_func(*args, **kwargs)
return wrapper_func
@decorator_func
def names_and_age(age1, age2, name1="Ben", name2="Harry"):
return f"{name1} is {age1} years old and {name2} is {age2} years old"
print(names_and_age(12,15,name1="Lily",name2="Kishor"))
there are 2 positional arguments and 2 keyword arguments Lily is 12 years old and Kishor is 15 years old
# Example:
def increase_decorator(func):
def increase_by_two():
print('increase by 2...')
new_number = func()
return new_number + 2
return increase_by_two
def decrease_decorator(func):
def decrease_by_one():
print('decrease by 1...')
new_number = func()
return new_number -1
return decrease_by_one
@increase_decorator
@decrease_decorator
def get_number():
return 5
print(get_number())
increase by 2... decrease by 1... 6
a*x^b
with *args
and **kwargs
¶# Example for a*x^b
# get_number will be get_number
# a_into_x will be a decorator
# x_raise2_b will be another decorator
def a_into_x(func):
def mul_a_x(*args, **kwargs):
print('multiply a to x...')
atuple = func(*args, **kwargs)
return atuple[0], atuple[1], atuple[2]*atuple[0]
return mul_a_x
def x_raise2_b(func):
def pow_x_b(*args, **kwargs):
print('raise power of x by b...')
btuple = func(*args, **kwargs)
return btuple[0], btuple[1], pow(btuple[2], btuple[1])
return pow_x_b
@a_into_x
@x_raise2_b
def get_number(a=1, b=1, x=None):
if x is None:
x = int(input("Give x for a*x^b: "))
return a, b, x
print(get_number(a=2, b=3))
multiply a to x... raise power of x by b... Give x for a*x^b: 4 (2, 3, 128)
*args
and **kwargs
¶def multiply_by_a(func):
def wrapper(a, b, x):
return a * func(a, b, x)
return wrapper
def raise_to_power_b(func):
def wrapper(a, b, x):
return func(a, b, x) ** b
return wrapper
@multiply_by_a
@raise_to_power_b
def calculate(a, b, x):
return x
result = calculate(2, 3, 4)
print(result)
128
Best example for decorators would be the routing in Flask
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'