As in Python, a function can return another function then we can take it’s benefit.
def our_parent_function(): print("Hello from parent function") def our_child_function(): print("Hello from child function") return our_child_function # returning child functionchild_function = our_parent_function()child_function() # calling child function>>> Hello from parent function>>> Hello from child functionHere, we are returning the reference of our_child_function, storing in child_function and calling it.
Interesting, No?Our first decoratorLet, we want to print our country’s name along with our name but we won’t change our describe_myself function.
def our_first_decorator(func): def wrapper(): # it can be any named, but according to convention "wrapper" matches better # do something here func("Mr.
Coder") # do something here print("Bangladesh") # do something here return wrapperdef describe_myself(name): print(name)describe_myself = our_first_decorator(describe_myself)describe_myself()>>> Mr.
Coder>>> BangladeshHere, our_first_decorator takes func as argument, modifies its behavior and returns a new function.
We are assigning the modified functions reference to describe_myself variable and calling through it.
Yes, this is our first decorator!.A decorator wraps a function by modifying its behavior.
We’ve done the same thing in the previous code.
Polish our first decoratorUsing our_first_decorator is not looking awesome yet.
We can use it in smarter way by using @ symbol (sometimes, @ is called pie-decorator) .
The following block of code will do the same as previous one.
def our_first_decorator(func): def wrapper(): func("Mr.
Coder") print("Bangladesh") return wrapper@our_first_decorator # nice, no?def describe_myself(name): print(name)describe_myself()Here we’ve hard coded “Bangladesh” in our wrapper function.
We can pass arguments for the wrapper too!def our_first_decorator(func): def wrapper(country): # updated func("Mr.
Coder") print(country) # updated return wrapper@our_first_decoratordef describe_myself(name): print(name)describe_myself("Bangladesh") # updatedIt looks nicer now!Introspection our decoratorLet’s introspect our function and decorator:def our_first_decorator(func): def wrapper(country): func("Mr.
Coder") print(country) return wrapper@our_first_decoratordef describe_myself(name): print(name)print(our_first_decorator)print(our_first_decorator.
__name__)>>> <function our_first_decorator at 0x7f6b039300d0>>>> our_first_decorator # looks fineprint(describe_myself)print(describe_myself.
__name__)>>> <function our_first_decorator.
<locals>.
wrapper at 0x7f6b03930268>>>> wrapper (why wrapper? shouldn't it be describe_myself?)Wait, for print(describe_myself.
__name__) it shows that name of describe_myself function is wrapper!.Shouldn’t it be describe_myself?However, after being decorated, describe_myself has gotten confused about its identity.
It reports of being the wrapper inner function of our_first_decorator.
Though this information is technically not wrong but not very useful information.
We can solve this issue by using @functools.
wraps decorator.
A custom decorator should use this decorator to preserve its information.
from functools import wrapsdef our_first_decorator(func): @wraps(func) # see this update def wrapper(country): func("Mr.
Coder") print(country) return wrapper@our_first_decoratordef describe_myself(name): print(name)print(describe_myself)print(describe_myself.
__name__)>>> <function describe_myself at 0x7f8b7f8391e0>>>> describe_myself # now it's fine!It looks fine now!In this brief discussion, we have completed some basics of python decorator and developed our first python decorator! :).