0% found this document useful (0 votes)
0 views50 pages

4.0 - Decorators in Python

The document provides an extensive overview of decorators in Python, explaining their purpose as design patterns that modify the behavior of functions, methods, or classes without altering their source code. It covers how to define decorators, use them with parameters, and implement them using the @ symbol for syntactic sugar, as well as how to create decorator classes and handle method decoration. Additionally, it discusses decorator factories for passing arguments to decorators, enhancing the functionality of Python programming.

Uploaded by

chakrasgt
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views50 pages

4.0 - Decorators in Python

The document provides an extensive overview of decorators in Python, explaining their purpose as design patterns that modify the behavior of functions, methods, or classes without altering their source code. It covers how to define decorators, use them with parameters, and implement them using the @ symbol for syntactic sugar, as well as how to create decorator classes and handle method decoration. Additionally, it discusses decorator factories for passing arguments to decorators, enhancing the functionality of Python programming.

Uploaded by

chakrasgt
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

Decorators in Python

CC3 – Object Oriented Programming


Review on Python
Python functions are lines of code that contain a specific set of instructions to reduce
redundancy when duplicating the usage of such instructions

An example of a simple function in Python would be:

def Hello():
print(“Hello world”)

Hello()
Hello()

The code above gives an output of:

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.

def add(x, y):


return x+y

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)

• first_message("Hello! Nice to meet you!")

• second_message = first_message
• second_message("Hello! How are you?")

• # Prints "Hello! Nice to meet you!"


• # Prints "Hello! How are you?"
Objects in Python
• When the code is run on the previous slide, both functions
make use of the print command.
• What is happening here is that the “first_message” and
“second_message” object refer to the same object.
• This is a characteristic of Python where we can an object as an
argument to another object.
Objects in Python
• If you noticed the syntax, we are not using parenthesis for
“first_message”.
• This is because we are not calling the function, we are passing
the function “first_message” into a variable “second_message.”
• This means that we can call the “first_message” function from
the variable “second_message”.
Objects in Python
• Another characteristic of Python is passing functions as
arguments to another function.
• These types of functions that take other functions as arguments
are also called higher order functions.
• Examples of these are the “map”, “filter”, and “reduce”
functions in Python.
Objects in Python
• An example of passing functions to other functions is shown below:
• def increment(num):
• return num + 1

• def decrement(num):
• return num - 1

• def operation(function, num):


• result = function(num)
• return result

• print(operation(increment, 10)) # Outputs 11


• print(operation(decrement, 15)) # Outputs 14
Objects in Python
• In the example in the previous slide, we are passing a function
as one of the arguments in the “operation” function.
• This will allow us to call the function in question in another
function.
• This allows us to call different functions depending on what is
passed to the “operation” function.
Objects in Python
• A function can also return another function, like so:
• def function1():
• def function2():
• print("function2 is returned by function1")
• return function2

• 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.

def outerFunc(func): def outerFunc(func):


def innerFunc(): def innerFunc():
func() func()
print(“How are you?”) print(“How are you?”)
return innerFunc return innerFunc

def hello(): @outerFunc


print(“Hello World”) def hello():
print(“Hello World”)
x = outerFunc(hello)
x() hello()
Decorators with parameters
Creating a divide function but giving one of the parameters as 0 would create a syntax
error. We can use a decorator that will check if it’s 0 then give a different output.
def outerFunc(func):
def divide(a, b):
def innerFunc(a, b):
return a/b
if b == 0:
print(“b must not be 0”)
x = divide(6, 0)
return
print(x)
return func(a, b)
return innerFunc
Output: Error
@outerFunc
def divide(a, b):
print(a/b)

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

• def adds_num(x, y):


• print(x + y)

• help = decorator(adds_num, 10, 15)


• help() # Prints 150 and 25
Decorator Functions
• The previous code shows that adding arguments to the
decorator allows us to manipulate data that is passed.
• This means we can pass data to a decorated function which
does its own computations.
• We can then pass those same data to the regular function.
• Take note that we must pass the arguments to the decorated
function and regular function, or we will encounter an error.
Syntactic Sugar
• Syntactic sugar is syntax within a programming language that
is designed to make features easier to read or express.
• It’s meant to make code “sweeter” so that the code is easier to
understand.
• These are usually in the form of special syntax to shorten certain
features in a programming language.
• Decorators in Python can benefit from syntactic sugar to make
the code simpler.
Using the @ symbol
• We can use the “@” symbol along with the name of the
decorator function.
• This is placed above the ordinary function to be “decorated”.
• This will allow us to shorten the process of decorating a given
function in our code.
• Let us look at some applications of this in the next set of slides.
Using the @ symbol
• An example of how to use the @ symbol is shown below:
• def decorator(function):
• def wrapper():
• print("This is a wrapper")
• function()
• 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

• def __call__(self, *args, **kwargs):


• print("The class method has been
decorated.")
• return self.func(*args, **kwargs)
Decorating Methods
• We can also decorate methods, with the use of the “__get__()”
method:
• def __get__(self, instance, cls):
• # Return a Method if it is called on an
instance
• return self if instance is None else
MethodType(self, instance)

• 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”)

You might also like