People have confusion about how to use python decorators in the proper way and how it works. The main reason for it is there are several ways to use decorators. In this article, we will discuss the basics of decoration and demonstrate all types of uses.
First, we will learn a few terminologies.
First-class objects in a programming language are entities that behave just like normal objects. They can be referenced by variables, stored in data structures like list or dict, passed as arguments to another function, returned as a value from another function.
In mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following:
- takes one or more functions as arguments (i.e. a procedural parameter, which is a parameter of a procedure that is itself a procedure),
- returns a function as its result.
All other functions are first-order functions.
Functions in Python are first class citizens. This means that they support operations such as being passed as an argument, returned from a function, modified, and assigned to a variable. This is a fundamental concept to understand before we delve into creating Python decorators. Let's checkout the code's from datacamp.com.
Assigning Functions to Variables
def plus_one(number):
return number + 1
add_one = plus_one
add_one(5)
"""
output:
6
"""
Defining Functions Inside other Functions
def plus_one(number):
def add_one(number):
return number + 1
result = add_one(number)
return result
plus_one(4)
"""
output:
5
"""
Passing Functions as Arguments to other Functions
def plus_one(number):
return number + 1
def function_call(function):
number_to_add = 5
return function(number_to_add)
function_call(plus_one)
"""
output:
6
"""
Functions Returning other Functions
def hello_function():
def say_hi():
return "Hi"
return say_hi
hello = hello_function()
hello()
"""
output:
Hi
"""
Creating Decorators
def my_decorator(func):
def wrapper():
print("Before the func call.")
func()
print("After the func call.")
return wrapper
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
say_hello()
"""
output:
Before the func call.
Hello!
After the func call.
"""
Now same thing will be done by using @ symbol
def my_decorator(func):
def wrapper():
print("Before the func call.")
func()
print("After the func call.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
"""
output:
Before the func call.
Hello!
After the func call.
"""
So, @my_decorator is just an easier way of saying say_whee = my_decorator(say_whee). It’s how you apply a decorator to a function.
Now I will demonstrate 7 types of decorator implementation examples. These are
- Class implementation of function decorator without argument
- Function implementation of function decorator without argument
- Class implementation of function decorator with argument
- Function implementation of function decorator with argument
- Class implementation of class decorator without argument
- Function implementation of class decorator without argument
- Decorator chaining
All these example codes are available at
github.com
Class implementation of function decorator without argument
class MyClassDecorator(object):
def __init__(self, f):
print('__init__() called | function:' + str(f.__name__))
self.f = f
def __call__(self, *args, **kwargs):
print('__call__() called | function:' + str(self.f.__name__) + ' | args: ' + str(args) + ' | kwargs:' + str(
kwargs))
self.f(*args, **kwargs)
@MyClassDecorator
def display_function(a, b, c):
print("display_function() called")
@MyClassDecorator
def display_function_no_call(a, b, c):
print("display_function_no_call() called")
print("Decoration finished for display_function() and display_function_no_call()")
display_function(1, 2, 3)
print("display_function() executed")
"""
output:
__init__() called | function:display_function
__init__() called | function:display_function_no_call
Decoration finished for display_function() and display_function_no_call()
__call__() called | function:display_function | args: (1, 2, 3) | kwargs:{}
display_function() called
display_function() executed
"""
Function implementation of function decorator without argument
def my_decorator(f):
print('my_decorator() called | function:' + str(f.__name__))
def wrapped(*args, **kwargs):
print('wrapped() called | function:' + str(f.__name__) + ' | args:' + str(args) + '| kwargs:' + str(kwargs))
f(*args) # calling the original function
return wrapped
@my_decorator
def display_function(a, b, c):
print("display_function() called")
@my_decorator
def display_function_no_call(a, b, c):
print("display_function_no_call() called")
print("Decoration finished for display_function() and display_function_no_call()")
display_function(1, 2, 3)
print("display_function() executed")
"""
output:
my_decorator() called | function:display_function
my_decorator() called | function:display_function_no_call
Decoration finished for display_function() and display_function_no_call()
wrapped() called | function:display_function | args:(1, 2, 3)| kwargs:{}
display_function() called
display_function() executed
"""
Class implementation of function decorator with argument
class MyClassDecorator(object):
def __init__(self, *deco_args, **deco_kwargs):
print('__init__() called | args: ' + str(deco_args) + ' | kwargs:' + str(deco_kwargs))
def __call__(self, f):
print('__call__() called | function:' + str(f.__name__))
def wrapped(*args, **kwargs):
print(
'wrapped() called | function:' + str(f.__name__) + ' | args:' + str(args) + '| kwargs:' + str(kwargs))
return f(*args, **kwargs)
return wrapped
@MyClassDecorator("arg1", "arg2")
def display_function(a, b, c):
print("display_function() called")
@MyClassDecorator("no_call_arg1", "no_call_arg2")
def display_function_no_call(a, b, c):
print("display_function_no_call() called")
print("Decoration finished for display_function() and display_function_no_call()")
display_function(1, 2, 3)
print("display_function() executed")
"""
output:
__init__() called | args: ('arg1', 'arg2') | kwargs:{}
__call__() called | function:display_function
__init__() called | args: ('no_call_arg1', 'no_call_arg2') | kwargs:{}
__call__() called | function:display_function_no_call
Decoration finished for display_function() and display_function_no_call()
wrapped() called | function:display_function | args:(1, 2, 3)| kwargs:{}
display_function() called
display_function() executed
"""
Function implementation of function decorator with argument
def my_decorator(*deco_args, **deco_kwargs):
print('my_decorator() called | args: ' + str(deco_args) + ' | kwargs:' + str(deco_kwargs))
def inner(f):
print('inner(f) called | function:' + str(f.__name__) + ' || ( Have access to deco_args: ' + str(
deco_args) + ' | deco_kwargs:' + str(deco_kwargs) + ')')
def wrapped(*args, **kwargs):
print(
'wrapped() called | function:' + str(f.__name__) + ' | args:' + str(args) + '| kwargs:' + str(
kwargs) + ' || ( Have access to deco_args: ' + str(
deco_args) + ' | deco_kwargs:' + str(deco_kwargs) + ')')
return f(*args, **kwargs) # calling the original function and return
return wrapped
return inner
@my_decorator("arg1", "arg2")
def display_function(a, b, c):
print("display_function() called")
@my_decorator("no_call_arg1", "no_call_arg2")
def display_function_no_call(a, b, c):
print("display_function_no_call() called")
print("Decoration finished for display_function() and display_function_no_call()")
display_function(1, 2, 3)
print("display_function() executed")
"""
output:
my_decorator() called | args: ('arg1', 'arg2') | kwargs:{}
inner(f) called | function:display_function || ( Have access to deco_args: ('arg1', 'arg2') | deco_kwargs:{})
my_decorator() called | args: ('no_call_arg1', 'no_call_arg2') | kwargs:{}
inner(f) called | function:display_function_no_call || ( Have access to deco_args: ('no_call_arg1', 'no_call_arg2') | deco_kwargs:{})
Decoration finished for display_function() and display_function_no_call()
wrapped() called | function:display_function | args:(1, 2, 3)| kwargs:{} || ( Have access to deco_args: ('arg1', 'arg2') | deco_kwargs:{})
display_function() called
display_function() executed
"""
Class implementation of class decorator without argument
class MyClassDecorator:
# accept the class as argument
def __init__(self, _class):
print('__init__() called | class:' + str(_class.__name__))
self._class = _class
# accept the class's __init__ method arguments
def __call__(self, name):
print('__call__() called | class:' + str(self._class.__name__) + ' | arg:' + str(name))
# define a new display method
def new_display(self):
print('new_display() called')
print('Name: ', self.name)
print('PIN: 1234')
# replace display with new_display
self._class.display = new_display
# return the instance of the class
obj = self._class(name)
print('returning modified class object')
return obj
@MyClassDecorator
class Employee:
def __init__(self, name):
print('original __init__() called' + ' | arg:' + str(name))
self.name = name
def display(self):
print('original display() called')
print('Name: ', self.name)
print("Decoration finished for Employee Class")
obj = Employee('Towhidul Haque Roni')
print("Employee obj created")
obj.display()
print("display() executed")
"""
output:
__init__() called | class:Employee
Decoration finished for Employee Class
__call__() called | class:Employee | arg:Towhidul Haque Roni
original __init__() called | arg:Towhidul Haque Roni
returning modified class object
Employee obj created
new_display() called
Name: Towhidul Haque Roni
PIN: 1234
display() executed
"""
Function implementation of class decorator without argument
def my_decorator(_class):
print('my_decorator() called for the class: ' + str(_class.__name__))
# define a new display method
def new_display(self):
print('new_display() called')
print('Name: ', self.name)
print('PIN: 1234')
# replace the display with new_display
# (if the display method did not exist in the class,
# the new_display would have been added to the class as the display method)
_class.display = new_display
# return the modified employee
print('returning modified class (not object)')
return _class
@my_decorator
class Employee:
def __init__(self, name):
print('original __init__() called' + ' | arg:' + str(name))
self.name = name
def display(self):
print('original display() called')
print('Name:', self.name)
print("Decoration finished for Employee Class")
obj = Employee('Towhidul Haque Roni')
print("Employee obj created")
obj.display()
print("display() executed")
"""
output:
my_decorator() called for the class: Employee
returning modified class (not object)
Decoration finished for Employee Class
original __init__() called | arg:Towhidul Haque Roni
Employee obj created
new_display() called
Name: Towhidul Haque Roni
PIN: 1234
display() executed
"""
Decorator chaining
def register(*decorators):
"""
This decorator is for chaining multiple decorators.
:param decorators:args(the decorators as arguments)
:return: callable object
"""
def register_wrapper(func):
for deco in decorators[::-1]:
func = deco(func)
func._decorators = decorators
return func
return register_wrapper
def deco1(f):
def wrapper(*args, **kwds):
print('-' * 100)
fn = f(*args, **kwds)
print('-' * 100)
return fn
return wrapper
def deco2(f):
def wrapper(*args, **kwds):
print('*' * 100)
fn = f(*args, **kwds)
print('*' * 100)
return fn
return wrapper
def deco3(f):
def wrapper(*args, **kwds):
print('#' * 100)
fn = f(*args, **kwds)
print('#' * 100)
return fn
return wrapper
class Foo(object):
@deco1
@deco2
@deco3
def bar(self):
print('I am bar')
class AnotherFoo(object):
@register(deco1, deco2, deco3)
def bar(self):
print('I am bar')
foo = Foo()
foo.bar()
print('\n\n~~~~ Alternate Way to Annotate ~~~~\n\n')
another_foo = AnotherFoo()
another_foo.bar()
print(another_foo.bar._decorators)
"""
output:
----------------------------------------------------------------------------------------------------
****************************************************************************************************
####################################################################################################
I am bar
####################################################################################################
****************************************************************************************************
----------------------------------------------------------------------------------------------------
~~~~ Alternate Way to Annotate ~~~~
----------------------------------------------------------------------------------------------------
****************************************************************************************************
####################################################################################################
I am bar
####################################################################################################
****************************************************************************************************
----------------------------------------------------------------------------------------------------
(<function deco1 at 0x7f50c7e6c940>, <function deco2 at 0x7f50c7e6c9d0>, <function deco3 at 0x7f50c7e6ca60>)
"""
You can comment any suggestions on it. If you enjoyed the article and want updates about my new article, please follow me.