Python decorator with easy examples to understand

Posted by

Outline 

  • What is decorator
  • Function as first-class objects
  • Basics of Closure
  • How decorator works
  • Decorator example
  • Property decorator
  • The setter

Introduction: what is a decorator?

python-decorator

Python has a very special feature that is called a decorator. Decorator adds new functionality to a function without modifying it. We can also say, it is a kind of layer around an existing function that adds something new to it.

Before going deep into a decorator, we shall have a quick look at python functions.

Function as first class objects

Functions in Python are first-class objects, which means we can use function just like another object or variable. In Python, a function can be assigned to another variable, it can return another function, it can be passed as an argument to another function.

Here are a few examples of how a function is treated as a first-class object:

Example 1: fx assigned to a variable

def fx():
    print(" some display of a function")
var=fx
var()

Example 2: a function returns a function

def outer_Function(x):
    print('display inside outerFunction {}'.format(x))
    def inner_Function(y):
        print("display inside innerFunction {}".format(y))
    return inner_Function
innerFuntion=outer_Function(4)
innerFuntion(5)

Note: outer_Function does not have access to argument Y but inner_Function has access to argument x.

The outer_Function is called first with argument 4 only, then the inner_Function is called with an argument 5

Example 3: a function passed as an argument to another function.

def function_one():
    print('this first function')
def function_two(function_one):
    function_one()
function_two(function_one)

Note:  Here, function_one is passed as an argument in function_two

Basics of Closure

A closure is a kind of function where even after outer function has returned the inner function is waiting to get executed. This is very similar to example 2, where a function return a function.

Below is one similar example of closure:

def outer_function(msg):
    def inner_function():
        print(msg)
    return inner_function

python_func=outer_function('Python')
java_func=outer_function('Java')
python_func()
java_func()

When we execute hi_func=outer_function(‘Hi’), the outer function takes “hi” as an argument and returns the inner function itself without executing it. Now the inner function is ready to be executed.

But when we execute bi_func(), the python interpreter going to printout the specific message that was passed.

How does decorator work?

decorators: it is just a function that takes another function as an argument and some functionality and returns it.

this happens without altering the source code that we passed

In the case below, it returns the wrapper_function that is waiting to get executed. when it is executed it prints the message.

In closure we passed a msg, but in this case, we shall pass a function as an argument

Decorator Example

Example 1: normal decorator without parameter

 

def decorator_function(original_function):
    def wrapper_function():
        print('wrapper executed this before {0}'.format(original_function.__name__))
        return original_function()
    return wrapper_function
def display():
    print("display function ran")

display=decorator_function(display)
display()

We can also use the following format to use decorator:

@decorator_function
def display():
    print("display function ran")
display()

 

Example 2: decorator with arguments:

 

def decorator_function(original_function):
    def wrapper_function(*agrs, **kwargs):
        print('wrapper executed this before {}'.format(original_function.__name__))
        return original_function(*agrs, **kwargs)
    return wrapper_function

@decorator_function
def display_info(name, age):
    print('display info ran with arguments ({}, {})'.format(name, age))
display_info('Jhon', 25)
@decorator_function
def display():
    print('display function ran')

Example 3: Decorator chaining

def deco_one(original_function):
    def inner_function():
        print("this is the first deco ")
        return original_function()
    return inner_function

def deco_two(original_function):
    def inner_function2():
        print("this is the second deco ")
        return original_function()
    return inner_function2

@deco_two

@deco_one
def display():

    print("this is inside display")
display()

Chaining the two decorators can be simplified as the following code:

display= deco_two(deco_one(display))

Example 4: Flask style arguments

Just put one more layer of function. The argument that is passed, in the outermost function, can be used for any operations inside the functions.

def arg_dec(arg):
    def deco_one(original_function):
        def inner_function():
            print(arg,"this is the first deco ")
            return original_function()
        return inner_function
    return deco_one
def deco_two(original_function):

    def inner_function2():

        print("this is the second deco ")

        return original_function()

    return inner_function2

@arg_dec('Testing')

@deco_two
def display():

    print("this is inside display")
#display= deco_one(deco_two(display))
display()

Property decorator:

It is a kind of decorator, which enables programmers to use the method as a property of a class.

Let say X is our sample class and it has a method called “display”.

Now, to use display, we have to use double parentheses. But if we use, property decorator, the python interpreter treats it as a class property.

 

class X:
    def display(self):
        print("hi, this is inside display")
obj_X=X()
obj_X.display()

 

After using property decorator:

class X:
    @property
    def display(self):
        print("hi, this is inside display")
obj_X=X()
obj_X.display

 The Setter:

class NameX:

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
     @property     
     def full_name(self):
         return self.first_name + ' ' + self.last_name    
     
 x=NameX('X', 'Y') 
 print(x.full_name)

x.full_name=”vladmir putin”  this will throw an error.

If using property decorator, then it makes a method behave like an attribute. But despite of this, full_name cant take any assignment.

Now to make this happen, we will use setter.

@full_name.setter

def full_name(self, name):

    self.first_name, self.last_name = name.split(" ")

Now we can make assignments as shown below:

x.full_name="vladmir putin"

print(x.full_name)

 

The full code looks something like this:

class name:

     def __init__(self, first_name, last_name):
          self.first_name = first_name
          self.last_name = last_name

@property
def full_name(self):
     return self.first_name + ' ' + self.last_name

@full_name.setter
def full_name(self, name):
     self.first_name, self.last_name = name.split(" ")

x=name('X', 'Y')
print(x.full_name)
x.full_name="vladmir putin"
print(x.full_name)

Few things to remember while using setter:

  1. property decorator must be present prior to the definition of setter.
  2. The name of setter must be same as the method on which decorator is placed.
  3. We have to use special keyword to change the functionality of the decorator “full_name”
@property
def full_name(self):
    return self.first_name + ' ' + self.last_name

@full_name.setter

def full_name(self, name):

    self.first_name, self.last_name = name.split(" ")

 

 

Leave a Reply

Your email address will not be published.