forked from trekhleb/learn-python
-
Notifications
You must be signed in to change notification settings - Fork 1
/
test_function_decorators.py
91 lines (68 loc) · 3.85 KB
/
test_function_decorators.py
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""Function Decorators.
@see: https://www.thecodeship.com/patterns/guide-to-python-function-decorators/
Function decorators are simply wrappers to existing functions. In the context of design patterns,
decorators dynamically alter the functionality of a function, method or class without having to
directly use subclasses. This is ideal when you need to extend the functionality of functions that
you don't want to modify. We can implement the decorator pattern anywhere, but Python facilitates
the implementation by providing much more expressive features and syntax for that.
"""
def test_function_decorators():
"""Function Decorators."""
# Function decorators are simply wrappers to existing functions. Putting the ideas mentioned
# above together, we can build a decorator. In this example let's consider a function that
# wraps the string output of another function by p tags.
# This is the function that we want to decorate.
def greeting(name):
return "Hello, {0}!".format(name)
# This function decorates another functions output with <p> tag.
def decorate_with_p(func):
def function_wrapper(name):
return "<p>{0}</p>".format(func(name))
return function_wrapper
# Now, let's call our decorator and pass the function we want decorate to it.
my_get_text = decorate_with_p(greeting)
# Here we go, we've just decorated the function output without changing the function itself.
assert my_get_text('John') == '<p>Hello, John!</p>' # With decorator.
assert greeting('John') == 'Hello, John!' # Without decorator.
# Now, Python makes creating and using decorators a bit cleaner and nicer for the programmer
# through some syntactic sugar There is a neat shortcut for that, which is to mention the
# name of the decorating function before the function to be decorated. The name of the
# decorator should be prepended with an @ symbol.
@decorate_with_p
def greeting_with_p(name):
return "Hello, {0}!".format(name)
assert greeting_with_p('John') == '<p>Hello, John!</p>'
# Now let's consider we wanted to decorate our greeting function by one more functions to wrap a
# div the string output.
# This will be our second decorator.
def decorate_with_div(func):
def function_wrapper(text):
return "<div>{0}</div>".format(func(text))
return function_wrapper
# With the basic approach, decorating get_text would be along the lines of
# greeting_with_div_p = decorate_with_div(decorate_with_p(greeting_with_p))
# With Python's decorator syntax, same thing can be achieved with much more expressive power.
@decorate_with_div
@decorate_with_p
def greeting_with_div_p(name):
return "Hello, {0}!".format(name)
assert greeting_with_div_p('John') == '<div><p>Hello, John!</p></div>'
# One important thing to notice here is that the order of setting our decorators matters.
# If the order was different in the example above, the output would have been different.
# Passing arguments to decorators.
# Looking back at the example before, you can notice how redundant the decorators in the
# example are. 2 decorators(decorate_with_div, decorate_with_p) each with the same
# functionality but wrapping the string with different tags. We can definitely do much better
# than that. Why not have a more general implementation for one that takes the tag to wrap
# with as a string? Yes please!
def tags(tag_name):
def tags_decorator(func):
def func_wrapper(name):
return "<{0}>{1}</{0}>".format(tag_name, func(name))
return func_wrapper
return tags_decorator
@tags('div')
@tags('p')
def greeting_with_tags(name):
return "Hello, {0}!".format(name)
assert greeting_with_tags('John') == '<div><p>Hello, John!</p></div>'