4.0 - Decorators in Python
4.0 - Decorators in Python
def Hello():
print(“Hello world”)
Hello()
Hello()
Hello world
Hello world
Review on Python
Functions in Python are easily changeable and are dynamic. Which means their
usage can change depending on how they are read by the interpreter.
def Hello():
print(“Hello world”)
print(“Hello world”)
Hello()
The code above is a changed version of the previous code but will still show the
same output:
Hello world
Hello world
Review on Python
Any data type supported by Python can be used inside a parameter of a function.
print(add(3, 5))
The code above uses the parameters of the function add x and y and returns their
value after calling the function. Although, simply calling add will not do anything as it
only returns the value. Therefore, we must use a print line to print the answer.
8
Review on Python
These are parameters that can be used when calling the function.
def Hello(answer):
print(answer)
Hello(“Hello world”)
Hello(“Hello world 2”)
The code above uses the parameters of the function and inputted a variable answer
with the value being the called function parameter of hello()
Hello world
Hello world 2
Objects in Python
• Before making use of decorators, let us first understand how
Python implements objects.
• In Python, everything is an object.
• These includes classes, functions, variables and so on.
• This allows us to pass functions as objects to another object.
Objects in Python
• An example of this behavior is shown below:
• def first_message(msg):
• print(msg)
• second_message = first_message
• second_message("Hello! How are you?")
• def decrement(num):
• return num - 1
• new_function = function1()
• new_function() # Outputs "function2 is return by
function1"
Objects in Python
• As seen in the previous slide, we can return another function as
our return value for another function.
• Take note that we are passing the function “function2” and not
calling it and getting just the output.
• This is like the previous example where we are passing a
function to a variable.
• This way, if we call “new_function”, it will mimic the output of
“function2”.
Objects in Python
• These examples show that we can pass functions to other
functions or return functions in Python.
• This allows us to pass the “functionality” of a function to
another function or an object.
• This can also serve as a way for us to combine similar functions
into one “main” function.
Decorator Functions
Decorators in Python
Defining Decorators
• Decorator functions are software design patterns.
• They dynamically alter the functionality of a function, method,
or class.
• They do this without having to directly use subclasses or
change the source code of the decorated function.
• Decorators augment the behavior of other functions or
methods.
Decorators
Assume we have the code:
def hello():
print(“Hello World”)
We want to change the function hello without changing anything within the code.
Specifically, we want to add another line in the function “How are you?”
The code would need to be decorated using another function that calls the hello
function within itself.
Decorators
We would then add the function:
def outerFunc(func):
def innerFunc():
The parameter of outerFunc will be
func()
called whenever the original function
print(“How are you?”)
wants to be used. The output of this
return innerFunc
code would then be:
def hello():
Hello World
print(“Hello World”)
How are you?
x = outerFunc(hello)
x()
Decorators
The decorator function is the x = outerFunc(hello). We can rewrite this as @outerFunc
before the function you wish to change.
divide(3, 0)
Decorator Functions
• We can use decorators to take in a function, add some
functionality and return it:
• def decorator_sample(function):
• def decorator_tool():
• print("This is adding a 'decoration' to our
function.")
• function()
• return decorator_tool
Decorator Functions
• We can use decorators to take in a function, add some
functionality and return it:
• def regular_function():
• print("I am a regular function.")
• regular_function()
• decorator = decorator_sample(regular_function)
• decorator()
Decorator Functions
• Let us examine how the decorator was used.
• First, we create the decorator that will “decorate” our regular
function.
• The “decorator_function” takes a function as an input as an
argument and then passes that to a wrapper.
• The wrapper then “decorates” the function that is passed and
passes it back as a new function.
Decorator Functions
• What will happen now is that when the “decorated” function is
called, we will be able to make use of the enhanced function.
• We can also make use of decorators to enhance functions which
have parameters.
• When doing this, we will need to pass arguments for both the
decorator and the “regular” function.
Decorator Functions
• An example of passing arguments with decorators is shown below:
• def decorator(function, x, y):
• def wrapper():
• print(x * y)
• function(x, y)
• return wrapper
• @decorator
• def regular_function():
• print("This is a regular function")
• regular_function()
• # Prints "This is a wrapper" first
• # and then prints "This is a regular function"
Using the @ symbol
• As we can see in the previous slide, the “@decorator” command
allows us to skip a few steps when calling our decorator.
• This allows us to shorten our code when applying a decorator
to a regular function.
• What the command does is tell Python to decorate the function
below it with the given decorator.
• When we call “regular_function”, we can see that it has already
been decorated for us.
Using the @ symbol
• Take note that the decorator must return another function as an
argument.
• Therefore, we define a new function inside the decorator and
return that when the decorator is called.
• If we don’t return a function (or a wrapper to be specific) we
would get an error in our code.
• This is demonstrated in the next slide.
Using the @ symbol
• If our decorator has no function that is return, then an error will
occur.
• def decorator(function):
• # No function is returned by this decorator
• pass
• @decorator
• def regular_function():
• print("This function will not be printed.")
•
• regular_function() # An error will occur
Using the @ symbol
• As seen in the previous slide, we will get an error as Python is
expecting a function to be returned.
• This is because of the way Python interprets the @ symbol.
• When we call the @ decorator, we are essentially telling Python
to run this piece of code:
• regular_function = decorator(regular_function)
• We are returning a decorated “regular_function” as the function
itself.
Decorator Classes
Decorators in Python
Decorator Classes
• We went over how to make a decorator function, now we will
go over how to make a decorator class.
• We use a similar syntax to create a decorator class with some
additions.
• Specifically, we make use of the “__call__()” method in our
decorator class.
• This method will enable us to write classes where the objects
behave like a function and can be called like a function.
Decorator Classes
• Here is an example of how we can create a decorator class:
• class DecoratorClass:
• def __init__(self, function):
• self.function = function
• def __call__(self):
• print("This is the Decorator Class.")
• print("It works similarly to a Decorator
Function.")
• callback = self.function()
• return callback
Decorator Classes
• Here is an example of how we can create a decorator class:
• @DecoratorClass
• def simple_function():
• print("I am a simple function.")
• simple_function()
• # Prints "This is the Decorator Class."
• # then prints "It works similarly to a Decorator
Function."
• # and then prints "I am a simple function."
Decorator Classes
• As we can see in the previous example, we are using the same
general syntax with our class to augment a function.
• Some key differences is we initialize the class with the function
argument:
• def __init__(self, function):
• self.function = function
• We also create a variable to hold the new function which we
then return:
• callback = self.function()
• return callback
Decorating Methods
• We can also decorate methods, with the use of the “__get__()”
method:
• from types import MethodType
• class Decorator:
• def __init__(self, func):
• self.func = func
• class Test:
• def __init__(self):
• pass
Decorating Methods
• We can also decorate methods, with the use of the “__get__()”
method:
• @Decorator
• def sample_method(self):
• pass
• a = Test()
• a.sample_method()
Decorating Methods
• The code shown in the previous slides allows us to decorate a
specific method in another class.
• Due to how this is implemented, we need to factor additional
arguments being passed by the method that is being called.
• This is what “*args” and “**kwargs” are used for in the
parameters of the “__call__()” method.
Decorators with Arguments
Decorators in Python
Decorators with Arguments
• Using the @ symbol makes it so our decorator can only take one
argument.
• This argument is the function being decorated.
• If we want to pass additional arguments to our decorator and
call it using the @ symbol, we need to use a decorator factory.
• A decorator factory is a function that returns a decorator.
Decorators with Arguments
• An example of how to create a decorator with arguments using
a decorator factory is shown below:
• def decoratorfactory(message):
• def decorator(function):
• def wrapper(*args, **kwargs):
• print('The super decorator wants to tell
you: {}'.format(message))
• return function(*args, **kwargs)
• return wrapper
• return decorator
Decorators with Arguments
• An example of how to create a decorator with arguments using
a decorator factory is shown below:
• @decoratorfactory('Like and subscribe')
• def simple_method():
• pass
• simple_method()
Decorators with Arguments
• As seen in the previous example, we are now wrapping our
entire decorator in the decorator factory.
• What the decorator factory does is allow us to pass additional
arguments to our decorator aside from the function.
• We can then also pass arguments to the @ symbol when we call
our decorator factory.
Decorators with parameters
We can substitute the parameters with *args and **kwargs to reduce time complexity
def my_decorator(func):
def wrapper(*args, **kwargs):
print("======= GENERAL INFORMATION =======")
result = func(*args, **kwargs)
print("=============== END =============== ")
return result
return wrapper
@my_decorator
def my_function(*args, **kwargs):
for arg in args:
print(arg)
for key, value in kwargs.items():
print(f"{key}: {value}")
my_function("Hello", "Goodmorning", Firstname = "Chari", Lastname = "Chin")
Multiple decorators
You can add multiple @ decorators for a function and the output would be duplicated
based on the instructions
def outerFunc(func):
def innerFunc(*args, **kwargs):
func(*args, **kwargs)
print(“How are you?”)
return
return innerFunc
@outerFunc
@outerFunc
def hello(answer):
print(answer)
hello(“Hello World”)