Decorator is a structural design pattern:
š¤š¤š¤š¤š¤
Don’t be confused! Decorator and Function Decorator have no relationship at all. However, In Python when people talk about decorator, mostly they are talking about Function Decorator.
What is Closure?
Closure is a technique of combining the callables (usually 2 functions) together prepare_recipe, updated_func
that:
-
The variables layer_3, layer_2, layer_1
need to be stored for later usage in a specific scope. It will be passed to the outer/enclosing/wrapper function.
-
The outer/enclosing/wrapper function prepare_recipe
stores variables. The inner/nested function will use the variables later.
-
The inner/nested function updated_func
can access the variables thanks to the executed outer/enclosing/wrapper function.
Similar to Closure’s technique, we also have Function Decorator
What is Function Decorator?
Function Decorator is a technique of combining the callables (usually 2 functions) together prepare_recipe, updated_func
that:
-
The origin/main function decorate_ice_cream
is deprecated and needs to be updated a bit. It will be passed to the outer/enclosing/wrapper function.
-
The outer/enclosing/wrapper function prepare_recipe
stores origin/main function. The inner/nested function will use the origin/main function later (Closure’s usage).
-
The inner/nested function updated_func
will:
- update behaviors around the origin/main function:
- modify the input before giving the input to the origin/main function:
- original input:
PopsicleIceCream
- modified input:
Almond(Condensed(Chocolate(
+ PopsicleIceCream
- modify the output of origin/main function before returning the final result:
- original output:
Almond(Condensed(Chocolate(PopsicleIceCream
- modified output:
Almond(Condensed(Chocolate(PopsicleIceCream
+ ))Milk)
- then return the new version of the origin/main function.
What is Decorator?
Decorator help to add/update the object PopsicleIceCream
dynamically by placing them inside the decorators ChocolateDecorator, CondensedMilkDecorator, AlmondDecorator
.
It’s like a Function Decorator, but here it applies to Class Instance.
Why use Closure?
Closure creates an environment to remember simple values layer_3, layer_2, layer_1
, it’s like the class’s usage!
Do you know functools.partial
? functools.partial
function is also an example of Closure’s usage.
Question: Class is the alternative to Closure, then when should we use Closure in lieu of Class?
Answer: For simplicity, you should use Class for all cases. Closure is just a technique in theory, it’s probably only mentioned a lot in pointless interviews. I’m still bitter about that interview -_-
Why use Function Decorator?
It helps reduce code duplication by wrapping common code into a bundle def prepare_recipe
, then simply adding @prepare_recipe
above the concrete functions. This makes the function’s code shorter and easier to update/refactor.
Question: What are the alternatives if I’m too lazy to create the Function Decorator?
Answer: You can make a long code with duplication or a little better solution is to split the big function into small functions, the choice is yours! However, let’s reconsider using Function Decorator! Business before pleasure!
Why use Decorator?
Easily transform the original popsicle
object into a almond_condensed_milk_chocolate_popsicle
multiple features object dynamically, without affecting anything to the origin.
Question: I can easily add more features to an object without using Decorator, right?
Answer: Of course, but Decorator is lovely in its own way because it keeps the code clean by wrapping an popsicle
object in new chocolate_popsicle
objects and repeating that condensed_milk_chocolate_popsicle, almond_condensed_milk_chocolate_popsicle
process endlessly without touching the old code!
When to use Closure?
Question: When should I use Closure?
Answer: When it’s necessary to create an instance in order to store values layer_3, layer_2, layer_1
and use decorate_ice_cream
those values later
1
2
3
4
5
6
7
8
9
10
11
12
13
|
popsicle_ice_cream = "PopsicleIceCream"
cone_ice_cream = "IceCreamCone"
print("[original ice creams] ")
print(popsicle_ice_cream)
print(cone_ice_cream)
# main function
def decorate_ice_cream(layer_3, layer_2, layer_1, ice_cream):
magic_number = 9
layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
decorated_ice_cream = f"{layer_3}({layer_2_a}({layer_1}({ice_cream})){layer_2_b})"
return decorated_ice_cream
|
Expected Output:
1
2
3
4
5
6
7
|
[original ice creams]
PopsicleIceCream
IceCreamCone
[decorated ice creams]
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(IceCreamCone))Milk)
|
When to use Function Decorator?
Question: When should I use Function Decorator?
Answer: When the environment values, input values, output values surrounding the main/deprecated function needs to be updated before and after execution.
1
2
3
4
5
6
7
8
9
10
|
popsicle_ice_cream = "PopsicleIceCream"
cone_ice_cream = "IceCreamCone"
print("[original ice creams] ")
print(popsicle_ice_cream)
print(cone_ice_cream)
# main function
def decorate_ice_cream(ice_cream):
return ice_cream
|
Expected Output:
1
2
3
4
5
6
7
|
[original ice creams]
PopsicleIceCream
IceCreamCone
[decorated ice creams]
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(IceCreamCone))Milk)
|
When to use Decorator?
Question: When should I use GoF Decorator?
Answer: When object properties need to be updated at runtime, but the Class structure remains origin.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class IceCream:
def taste(self):
return "IceCream"
class PopsicleIceCream(IceCream):
def taste(self):
return "PopsicleIceCream"
class ConeIceCream(IceCream):
def taste(self):
return "ConeIceCream"
popsicle = PopsicleIceCream()
cone = ConeIceCream()
print("[original ice creams] ")
print(popsicle.taste())
print(cone.taste())
|
Expected Output:
1
2
3
4
5
6
7
|
[original ice creams]
PopsicleIceCream
ConeIceCream
[decorated ice creams]
Almond(Condensed(Chocolate(PopsicleIceCream))Milk)
Almond(Condensed(Chocolate(ConeIceCream))Milk)
|
How to implement Closure?
Non-Closure Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Recipe:
def __init__(self, func, layer_3, layer_2, layer_1):
self.func = func
self.layer_3 = layer_3
self.layer_2 = layer_2
self.layer_1 = layer_1
def decorate_ice_cream(self, ice_cream):
return self.func(self.layer_3, self.layer_2, self.layer_1, ice_cream)
if __name__ == "__main__":
recipe = Recipe(decorate_ice_cream, "Almond", "CondensedMilk", "Chocolate")
almond_condensed_milk_chocolate_popsicle = recipe.decorate_ice_cream(popsicle_ice_cream)
almond_condensed_milk_chocolate_cone = recipe.decorate_ice_cream(cone_ice_cream)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle)
print(almond_condensed_milk_chocolate_cone)
|
Closure Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# closure
def prepare_recipe(func, layer_3, layer_2, layer_1):
def updated_func(ice_cream):
return func(layer_3, layer_2, layer_1, ice_cream)
return updated_func
if __name__ == "__main__":
# closure's outcome: updated function
recipe = prepare_recipe(decorate_ice_cream, "Almond", "CondensedMilk", "Chocolate")
almond_condensed_milk_chocolate_popsicle = recipe(popsicle_ice_cream)
almond_condensed_milk_chocolate_cone = recipe(cone_ice_cream)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle)
print(almond_condensed_milk_chocolate_cone)
|
Source Code: Closure
How to implement Function Decorator?
Non-Function Decorator Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# main function
def decorate_ice_cream(ice_cream):
magic_number = 9
layer_3, layer_2, layer_1 = "Almond", "CondensedMilk", "Chocolate"
layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
front_ice_cream = f"{layer_3}({layer_2_a}({layer_1}("
back_ice_cream = f")){layer_2_b})"
half_decorated_ice_cream = front_ice_cream + ice_cream
decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
return decorated_ice_cream
if __name__ == "__main__":
almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle)
print(almond_condensed_milk_chocolate_cone)
|
Function Decorator Implementation:
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
|
# decorator
def prepare_recipe(func):
def updated_func(ice_cream):
magic_number = 9
layer_3, layer_2, layer_1 = "Almond", "CondensedMilk", "Chocolate"
layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
front_ice_cream = f"{layer_3}({layer_2_a}({layer_1}("
back_ice_cream = f")){layer_2_b})"
half_decorated_ice_cream = func(front_ice_cream + ice_cream)
decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
return decorated_ice_cream
return updated_func
@prepare_recipe
# main function
def decorate_ice_cream(ice_cream):
return ice_cream
if __name__ == "__main__":
almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle)
print(almond_condensed_milk_chocolate_cone)
|
Source Code: Function Decorator
How to implement Function Decorator With Params?
Theory about Function Decorator without params:
A) If we have:
1
2
|
def decorate_ice_cream(ice_cream):
return ice_cream
|
B) We can create decorator like this:
1
2
3
4
|
def prepare_recipe(func):
def updated_func(ice_cream):
return ... func(...) ...
return updated_func
|
C) Then apply decorator to the main function by:
1
2
3
|
@prepare_recipe
def decorate_ice_cream(ice_cream):
return ice_cream
|
or
1
|
decorate_ice_cream = prepare_recipe(decorate_ice_cream)
|
Theory about Function Decorator with params:
Those params need to be stored. It’s time for Closure to show off!
prepare_recipe = prepare_recipe_with_params(layer_3, layer_2, layer_1)
- Function Decorator With Params = Closure + Function Decorator Without Params
A) If we have:
1
2
|
def decorate_ice_cream(ice_cream):
return ice_cream
|
B) We can create decorator like this:
closure will create environment to store layer_3, layer_2, layer_1
1
2
3
4
5
6
7
8
|
# closure
def prepare_recipe_with_params(layer_3, layer_2, layer_1):
# decorator
def prepare_recipe(func):
def updated_func(ice_cream):
return ... func(...) ...
return updated_func
return prepare_recipe
|
C) Then apply decorator to the main function by:
1
2
3
|
@prepare_recipe(layer_3, layer_2, layer_1)
def decorate_ice_cream(ice_cream):
return ice_cream
|
or
1
2
3
|
# closure's outcome:
prepare_recipe = prepare_recipe_with_params(layer_3, layer_2, layer_1)
decorate_ice_cream = prepare_recipe(decorate_ice_cream)
|
Function Decorator With Params Implementation:
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
29
|
# decorator with params
def prepare_recipe_with_params(layer_3, layer_2, layer_1):
def prepare_recipe(func):
def updated_func(ice_cream):
magic_number = 9
layer_2_a, layer_2_b = layer_2[:magic_number], layer_2[magic_number:]
front_ice_cream = f"{layer_3}({layer_2_a}({layer_1}("
back_ice_cream = f")){layer_2_b})"
half_decorated_ice_cream = func(front_ice_cream + ice_cream)
decorated_ice_cream = half_decorated_ice_cream + back_ice_cream
return decorated_ice_cream
return updated_func
return prepare_recipe
@prepare_recipe_with_params("Almond", "CondensedMilk", "Chocolate")
# main function
def decorate_ice_cream(ice_cream):
return ice_cream
if __name__ == "__main__":
almond_condensed_milk_chocolate_popsicle = decorate_ice_cream(popsicle_ice_cream)
almond_condensed_milk_chocolate_cone = decorate_ice_cream(cone_ice_cream)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle)
print(almond_condensed_milk_chocolate_cone)
|
Source Code: Function Decorator With Params Manually
Source Code: Function Decorator With Params
How to implement Decorator?
Non-Decorator Implementation:
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
29
30
31
32
33
34
35
36
37
|
def generate(ice_cream, flavor):
old_taste = ice_cream.taste()
if "Chocolate" == flavor:
def _taste_chocolate():
return f"Chocolate({old_taste})"
ice_cream.taste = _taste_chocolate
elif "CondensedMilk" == flavor:
def _taste_condensed_mild():
return f"Condensed({old_taste})Milk"
ice_cream.taste = _taste_condensed_mild
elif "Almond" == flavor:
def _taste_almond():
return f"Almond({old_taste})"
ice_cream.taste = _taste_almond
return ice_cream
if __name__ == "__main__":
chocolate_popsicle = generate(popsicle, "Chocolate")
chocolate_cone = generate(cone, "Chocolate")
condensed_milk_chocolate_popsicle = generate(chocolate_popsicle, "CondensedMilk")
condensed_milk_chocolate_cone = generate(chocolate_cone, "CondensedMilk")
almond_condensed_milk_chocolate_popsicle = generate(
condensed_milk_chocolate_popsicle, "Almond"
)
almond_condensed_milk_chocolate_cone = generate(
condensed_milk_chocolate_cone, "Almond"
)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle.taste())
print(almond_condensed_milk_chocolate_cone.taste())
|
Decorator Implementation:
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
class Decorator(IceCream):
_component = None
def __init__(self, component):
print(f"{type(self)} is decorating the {type(component)}")
self._component = component
@property
def component(self):
return self._component
def taste(self):
return self._component.taste()
class ChocolateDecorator(Decorator):
def taste(self):
return f"Chocolate({self.component.taste()})"
class CondensedMilkDecorator(Decorator):
def taste(self):
return f"Condensed({self.component.taste()})Milk"
class AlmondDecorator(Decorator):
def taste(self):
return f"Almond({self.component.taste()})"
if __name__ == "__main__":
chocolate_popsicle = ChocolateDecorator(popsicle)
chocolate_cone = ChocolateDecorator(cone)
condensed_milk_chocolate_popsicle = CondensedMilkDecorator(chocolate_popsicle)
condensed_milk_chocolate_cone = CondensedMilkDecorator(chocolate_cone)
almond_condensed_milk_chocolate_popsicle = AlmondDecorator(
condensed_milk_chocolate_popsicle
)
almond_condensed_milk_chocolate_cone = AlmondDecorator(
condensed_milk_chocolate_cone
)
print("\n[decorated ice creams] ")
print(almond_condensed_milk_chocolate_popsicle.taste())
print(almond_condensed_milk_chocolate_cone.taste())
|
Source Code: Decorator