In [38]:
#https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators?rq=1
#https://dbader.org/blog/python-decorators
To understand decorators, you must first understand that:
In [1]:
def shout(word="yes"):
return word.capitalize()+"!"
In [2]:
shout()
Out[2]:
In [3]:
# As an object, you can assign the function to a variable like any other object
scream = shout
In [5]:
# Notice we don't use parentheses: we are not calling the function,
# we are putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream"
scream
Out[5]:
In [6]:
scream()
Out[6]:
In [9]:
#You can also remove the old name 'shout', and the function
# will still be accessible from 'scream'
del shout
print(shout())
In [8]:
scream()
Out[8]:
In [10]:
def talk():
def whisper(word="yes"):
return word.lower() + '...'
print(whisper())
In [11]:
talk()
In [12]:
# But "whisper" DOES NOT EXIST outside "talk":
whisper()
In [13]:
def getTalk(kind="shout"):
# We define functions on the fly
def shout(word="yes"):
return word.capitalize()+"!"
def whisper(word="yes") :
return word.lower()+"...";
# Then we return one of them
if kind == "shout":
# We don't use "()", we are not calling the function,
# we are returning the function object
return shout
else:
return whisper
In [14]:
getTalk()
Out[14]:
In [15]:
talk = getTalk()
In [16]:
talk
Out[16]:
In [17]:
talk()
Out[17]:
In [20]:
getTalk()("HellO")
Out[20]:
With getTalk() we return the shout function and then with ("HellO"), we pass the argument at shout function
If you can return a function, you can pass one as a parameter
In [21]:
def doSomethingBefore(func):
print("I do something before then I call the function you gave me")
print(func())
doSomethingBefore(scream)
Decorators are “wrappers”, which means that they let you execute code before and after the function they decorate without modifying the function itself.
Decorators allow you to define reusable building blocks that can change or extend the behavior of other functions. And they let you do that without permanently modifying the wrapped function itself. The function’s behavior changes only when it’s decorated.
In basic terms, a decorator is a callable that takes a callable as input and returns another callable.
The following function has that property and could be considered the simplest decorator one could possibly write:
In [49]:
def null_decorator(func):
print("I'm decorating")
return func
def greet():
return 'Hello!'
In [50]:
greeting= null_decorator(greet)
greeting()
Out[50]:
null_decorator is a callable (it’s a function), it takes another callable as its input, and it returns the same input callable without modifying it.
In [51]:
@null_decorator
def greet():
return 'Hello!'
greet()
Out[51]:
In [53]:
def uppercase(func):
def wrapper():
original_result = func()
modified_result = original_result.upper()
return modified_result
return wrapper
In [54]:
@uppercase
def greet():
return 'Hello!'
In [55]:
greet()
Out[55]:
In [56]:
# Template
def proxy(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
In [57]:
def trace(func):
def wrapper(*args, **kwargs):
print(f'TRACE: calling {func.__name__}() '
f'with {args}, {kwargs}')
original_result = func(*args, **kwargs)
print(f'TRACE: {func.__name__}() '
f'returned {original_result!r}')
return original_result
return wrapper
In [58]:
@trace
def say(name, line):
return f'{name}: {line}'
In [59]:
say('Jane', 'Hello, World')
Out[59]:
In [33]:
def bread(func):
def wrapper():
print("</''''''\>")
func()
print("<\______/>")
return wrapper
def ingredients(func):
def wrapper():
print("#tomatoes#")
func()
print("~salad~")
return wrapper
@bread
@ingredients
def sandwich(food="--ham--"):
print(food)
In [34]:
sandwich()
In [36]:
# The decorator to make it bold
def makebold(fn):
# The new function the decorator returns
def wrapper():
# Insertion of some code before and after
return "<b>" + fn() + "</b>"
return wrapper
# The decorator to make it italic
def makeitalic(fn):
# The new function the decorator returns
def wrapper():
# Insertion of some code before and after
return "<i>" + fn() + "</i>"
return wrapper
@makebold
@makeitalic
def say():
return "hello"
In [37]:
say()
Out[37]:
In [39]:
### Hard coding
# A decorator is a function that expects ANOTHER function as parameter
def my_shiny_new_decorator(a_function_to_decorate):
# Inside, the decorator defines a function on the fly: the wrapper.
# This function is going to be wrapped around the original function
# so it can execute code before and after it.
def the_wrapper_around_the_original_function():
# Put here the code you want to be executed BEFORE the original function is called
print("Before the function runs")
# Call the function here (using parentheses)
a_function_to_decorate()
# Put here the code you want to be executed AFTER the original function is called
print("After the function runs")
# At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
# We return the wrapper function we have just created.
# The wrapper contains the function and the code to execute before and after. It’s ready to use!
return the_wrapper_around_the_original_function
# Now imagine you create a function you don't want to ever touch again.
def a_stand_alone_function():
print("I am a stand alone function, don't you dare modify me")
a_stand_alone_function()
#outputs: I am a stand alone function, don't you dare modify me
In [40]:
# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in
# any code you want and return you a new function ready to be used:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
In [41]:
@my_shiny_new_decorator
def another_stand_alone_function():
print("Leave me alone")
In [42]:
another_stand_alone_function()
In [ ]: