Skip to main content

Decorators in Python

Decorators

In simple words, Decorator is a function that calls another function. 

Decorators are very useful concept in Python. Decorators allow the programmers to enhance the functionality of a function by wrapping it with a decorator. 

It is similar to having a function with in a function. Before we see more about decorators let's have a quick look at how it is like to wrap a function within another function and how a function can be assigned like an object. These will help in understanding the decorators better. 

Assigning function as an Object

Like creating instance of a class (object), the same can be done with functions as well. 

We can define a function and create instances of a function. Function can either be called directly or by using the instance created. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# Define a function to print "Hello World"

def hello_world():

   print("Hello World, This is a original function")



# Call the function directly

hello_world()


# Create instance of the function

say_hello = hello_world


# Calling the instance of a function

print("--Calling the instance of a function--")

say_hello()



Result

Hello World, This is a original function

--Calling the instance of a function--

Hello World, This is a original function


In the above example, 
  • Line - 2: Defining 'hello_world' function without any arguments. 
  • Line - 7: Calling the function 'hello_world'.
  • Line - 10: Assigning the function 'hello_world' with a different name 'say_hello'. 
    • One thing to note here is while assigning a function to create an instance, We should not specify round brackets after the function name. 
    • Mentioning the round brackets would call the function and assigns the return value to the variable. 
  • Line - 13: Print statement before calling the instance of a function. 
  • Line - 14: Calling the function 'say_hello' which also executes the functionality provided with in 'hello_world'.

One thing to note here is, assigning the function would create a new function and would be able to execute even after the deletion of the original function. 

16

17

18

19

20

# Delete the function 'hello_world'

del hello_world


say_hello()



Wrap a function with another function

Let's have a look at creating a function to wrap another function. This is usually required when the wrapper function need to contain some additional functionality. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# Define a function to print "Hello World"

def hello_world():

   print("Hello World, This is a original function")



# Wrapper function

def say_hello():

   print("--Inside wrapper function--")

   hello_world()

   print("--End of wrapper function--")



# Call Wrapper function to call hello_world

say_hello()



Result


--Inside wrapper function--

Hello World, This is a original function

--End of wrapper function--


In the above example, 
  • Line - 2: Defining the 'hello_world' function which just prints a message. 
  • Line - 7: Defining another function 'say_hello' with some additional messages. 
    • Lines - 8 & 10: To understand better, We are only printing Start and End of a wrapper function. 
  • Line - 9: Calling 'hello_world' function, which prints "Hello World" message. 
  • Line - 14: Calling 'say_hello' function to call 'hello_world' with added functionality from wrapper function.
This is easy and straight forward. However, if we need to create a wrapper for 10 functions or even more, we would need to create 10 different functions. 

Passing Function as parameter to wrapper function

One solution for this is passing function as a parameter. Let's have a look at an example by having two different functions and one wrapper function. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

# Define a function to print "Hello World"

def hello_world():

   print("Hello World, This is a original function")



def hello():

   print("Hello, This is another function")



# Wrapper function

def say_hello(new_function):

   print("--Inside wrapper function--")

   new_function()

   print("--End of wrapper function--")



# Call Wrapper function by passing function as parameter

say_hello(hello_world)


# Call Wrapper function by passing function as parameter

say_hello(hello)



Result


--Inside wrapper function--

Hello World, This is a original function

--End of wrapper function--

--Inside wrapper function--

Hello, This is another function

--End of wrapper function--


In the above example, 
  • Line - 2 & 6: Two different functions defined with different message to be printed. 
  • Line - 11: Defining a wrapper function 'say_hello' by passing a function as an argument. 
    • 'new_function' is an argument and function can be called by using this name. 
  • Line - 13: Calling the function by using the name mentioned in the arguments. 
    • Function called would be the function passed. 
  • Line - 18: Call function 'say_hello' and pass function 'hello_world' as an argument. 
    • One thing to note here, round brackets should not be used after the function in the arguments. 
  • Line - 21: Call function 'say_hello' and pass function 'hello' as an argument. 
    • One thing to note here, round brackets should not be used after the function in the arguments. 
However, Where ever the original function is called, the code need to be modified to call wrapper function instead. 

This can be avoided by using decorators. 

Creating a decorator

Using decorators makes this process much easier. 
  • Decorators can be used with functions that require arguments.
  • Calling programs doesn't require to be modified to call the new wrapper function. 
  • Same decorator can be with different functions. 
Let's have a look at an example of a decorator for a function with no arguments. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

# Create a Decorator

def say_hello_decorator(new_function):


   # Wrapper function inside a decorator

   def wrapper():

       print("--Inside wrapper function--")

       new_function()

       print("--End of wrapper function--")


   return wrapper



# Define a function to print "Hello World"

# Add a decorator before the function definition

@say_hello_decorator

def hello_world():

   print("Hello World, This is a original function")



# When decorator is mentioned with the function

# function can be called on it's down

# Wrapper in the decorator would be executed automatically

hello_world()



Result


--Inside wrapper function--

Hello World, This is a original function

--End of wrapper function--


In the above example, 
  • Line - 1: Defining a decorator with a function as an argument. 
    • Decorator need to be mentioned against the function by using '@'. 
    • This function would be referred with the name in the arguments in the decorator. 
  • Line - 5: Defining a wrapper function with arguments needed for the original function to be called. 
    • No arguments passed in this case as the original function 'hello_world' doesn't require any arguments to be passed. 
  • Lines - 6 & 8: Print statements to print the message for beginning and end of decorator wrapper function. Can be any code required to be performed as part of wrapper function. 
  • Line - 7: Function call, with the function name passed in the arguments. 
  • Line - 10: Return of wrapper, name of the function (with out round brackets). This is required to execute the wrapper function defined inside decorator function. 
  • Line - 16: Defining the function 'hello_world'. There is no change in the way function is coded. If decorator needs to be used, this needs to be added before the function definition. 
  • Line - 15: Mentioning decorator against the function, name of the decorator function by prefixing with '@'. 
    • This makes the whole process easier compared to writing the wrapper to enclose the function directly. 
    • No change needs to be done in the way original function is called (Line - 23). 
    • If this line is removed, the original function call will only execute the function defined and not decorator. 
  • Line - 23: No change to be done in the calling program. This is a huge advantage when the function is being used in large number of programs. 

Creating a decorator for a function with arguments

In the previous example, we have seen how to create a decorator for a function with no arguments. 

So, How to deal with a function with arguments? Wrapper function inside the decorator needs to be created with the required arguments. Arguments passed to the original function would automatically be passed over to the wrapper function in decorator. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

# Create a Decorator

def say_hello_decorator(new_function):


   # Wrapper function inside a decorator

   def wrapper(name):

       print("--Inside wrapper function--")

       new_function(name)

       print("--End of wrapper function--")


   return wrapper



# Define a function to print "Hello World"

# Add a decorator before the function definition

@say_hello_decorator

def hello_world(name):

   print(f"Hello World, {name} here.")



# When decorator is mentioned with the function

# function can be called on it's down

# Wrapper in the decorator would be executed automatically

hello_world("Pradeep")



Result


--Inside wrapper function--

Hello World, Pradeep here.

--End of wrapper function--


In the above example, we have added an argument to the original function and the wrapper function. 
  • Line - 16: Defining the function 'hello_world' with one argument. No change in the way decorator is mentioned before the function definition. 
  • Line - 5: Defining the wrapper function inside the decorator with the arguments needed. Arguments passed to the original function (Line - 23) will be passed over to the wrapper inside the decorator. 
  • Line - 23: Calling the the function by passing the required arguments. 
There is no other change required to this process. 

Creating a decorator for a function with return value

What if the function needs to return a value back to the calling program? Return value needs to be captured and returned at the end of the wrapper function. 

One thing to note here is, if the return value is mentioned in middle of wrapper function, execution would stop there (like any other function). 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

# Create a Decorator

def say_hello_decorator(new_function):


   # Wrapper function inside a decorator

   def wrapper(name):

       print("--Inside wrapper function--")

       ret = new_function(name)

       print("--End of wrapper function--")

       return ret


   return wrapper



# Define a function to print "Hello World"

# Add a decorator before the function definition

@say_hello_decorator

def hello_world(name):

   print(f"Hello World, {name} here.")

   return "Successful"



# When decorator is mentioned with the function

# function can be called on it's down

# Wrapper in the decorator would be executed automatically

ret_value = hello_world("Pradeep")


print(ret_value)



Result


--Inside wrapper function--

Hello World, Pradeep here.

--End of wrapper function--

Successful


In the above example, we have only done couple of changes to include return statement in the original function and in the wrapper function. 
  • Line - 19: Adding a return statement inside the original function.
  • Line - 7: Assigning the return value to the variable inside the wrapper function. 
  • Line - 9: Returning the value from the wrapper function. This would return the return value back to the calling function. 
  • Line - 25: Assigning the return value of the function. No change needs to be done for this when a decorator is added to the function. 

Hope the above info was useful in understanding the how decorators work in Python. 


If you have any Suggestions or Feedback, Please leave a comment below or use Contact Form.

Comments

  1. well defined , easy to understand

    ReplyDelete
  2. Decorator in the last example is useless. The same result without @say_hello_decorator. This decorator only adds one output string before and another string after "Hello World, Pradeep here." Not good example.

    ReplyDelete
    Replies
    1. Hi Andyrey,

      Thanks for sharing your inputs.

      I meant to keep it simple and easy to understand with as much of coding as possible.

      Thanks

      Delete
  3. Very well explained with simple examples. Keep up the good work!

    ReplyDelete

Post a Comment

Popular posts from this blog

All about READ in RPGLE & Why we use it with SETLL/SETGT?

READ READ is one of the most used Opcodes in RPGLE. As the name suggests main purpose of this Opcode is to read a record from Database file. What are the different READ Opcodes? To list, Below are the five Opcodes.  READ - Read a Record READC - Read Next Changed Record READE - Read Equal Key Record READP - Read Prior Record READPE - Read Prior Equal Record We will see more about each of these later in this article. Before that, We will see a bit about SETLL/SETGT .  SETLL (Set Lower Limit) SETLL accepts Key Fields or Relative Record Number (RRN) as Search Arguments and positions the file at the Corresponding Record (or Next Record if exact match isn't found).  SETGT (Set Greater Than) SETGT accepts Key Fields or Relative Record Number (RRN) as Search Arguments and positions the file at the Next Record (Greater Than the Key value). Syntax: SETLL SEARCH-ARGUMENTS/KEYFIELDS FILENAME SETGT  SEARCH-ARGUMENTS/KEYFIELDS FILENAME One of the below can be passed as Search Arguments. Key Fiel

What we need to know about CHAIN (RPGLE) & How is it different from READ?

CHAIN READ & CHAIN, These are one of the most used (& useful) Opcodes by any RPG developer. These Opcodes are used to read a record from file. So, What's the difference between CHAIN & READ?   CHAIN operation retrieves a record based on the Key specified. It's more like Retrieving Random record from a Database file based on the Key fields.  READ operation reads the record currently pointed to from a Database file. There are multiple Opcodes that start with READ and all are used to read a record but with slight difference. We will see more about different Opcodes and How they are different from each other (and CHAIN) in another article. Few differences to note.  CHAIN requires Key fields to read a record where as READ would read the record currently pointed to (SETLL or SETGT are used to point a Record).  If there are multiple records with the same Key data, CHAIN would return the same record every time. READE can be used to read all the records with the specified Ke

Extract a portion of a Date/Time/Timestamp in RPGLE - IBM i

%SUBDT Extracting Year, Month, Day, Hour, Minutes, Seconds or Milli seconds of a given Date/Time/Timestamp is required most of the times.  This can be extracted easily by using %SUBDT. BIF name looks more similar to %SUBST which is used to extract a portion of string by passing from and two positions of the original string. Instead, We would need to pass a value (i.e., Date, Time or Timestamp ) and Unit (i.e., *YEARS, *MONTHS, *DAYS, *HOURS, *MINUTES, *SECONDS or *MSECONDS) to %SUBDT.  Valid unit should be passed for the type of the value passed. Below are the valid values for each type. Date - *DAYS, *MONTHS, *YEARS Time - *HOURS, *MINUTES, *SECONDS Timestamp - *DAYS, *MONTHS, *YEARS, *HOURS, *MINUTES, *SECONDS, *MSECONDS Syntax: %SUBDT(value : unit { : digits { : decpos} }) Value and Unit are the mandatory arguments.  Digits and Decimal positions are optional and can only be used with *SECONDS for Timestamp. We can either pass the full form for the unit or use the short form. Below i