7 Things I Never Knew About Decorators (Python) Until Recently

Day 77 of experimenting with video content
Free AI web copilot to create summaries, insights and extended knowledge, download it at here
7553
Abstract
need to manually type <code>logger.info(whatever)</code> every time we call the <code>greet</code> function.</p><h1 id="6749">5) @cache</h1><p id="cda0">Let’s write a recursive function <code>fib(n)</code> that returns the nth fibonacci number.</p><p id="a3bf">The fibonacci number sequence starts with <code>[0, 1]</code>, and every subsequent number is the sum of the previous 2 numbers. It goes <code>[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...]</code></p><div id="f0ac"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">fib</span>(<span class="hljs-params">n</span>): <span class="hljs-keyword">if</span> n == <span class="hljs-number">1</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">if</span> n == <span class="hljs-number">2</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> <span class="hljs-keyword">return</span> fib(n-<span class="hljs-number">1</span>) + fib(n-<span class="hljs-number">2</span>)</pre></div><p id="9e91">^ here’s a recursive implementation of <code>fib(n)</code>. Note that if we call <code>fib(5)</code>, we have quite a number of function calls.</p><div id="44b6"><pre><span class="hljs-function"><span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">5</span>)</span> = <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">4</span>)</span> = [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span>] = [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span> + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>]] = [0+1] + [1 + [0+1]] = 3</span></pre></div><p id="c0aa">Here, notice that <code>f(3)</code> is computed twice, which isn’t very efficient. And this problem gets worse the higher our <code>n</code> becomes. Take <code>f(7)</code>:</p><div id="c749"><pre><span class="hljs-function"><span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">7</span>)</span> = <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">5</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">6</span>)</span> = [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">4</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">4</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">5</span>)</span>] = [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span>] + [<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span> + <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">4</span>)</span>] = <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+[<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>]+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+[<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>]+[<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>]+[<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">3</span>)</span>] = <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>+[<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span>+<span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-number">2</span>)</span>] = 0+1+1+0+1+1+0+1+0+1+1+0+1 = 8</span></pre></div><p id="9f5d">if <code>n</code> goes any higher, i don’t really wanna type it out. But you can see that there are many many repeat computations in this process. But <code>@cache</code> can get rid of these repeat computations.</p><div id="2b6b"><pre><span class="hljs-keyword">from</span> functools <span class="hljs-keyword">import</span> cache
<span class="hljs-meta">@cache</span> <span class="hljs-keyword">def</span> <span class="hljs-title fun
Options
ction_">fib</span>(<span class="hljs-params">n</span>): <span class="hljs-keyword">if</span> n == <span class="hljs-number">1</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">if</span> n == <span class="hljs-number">2</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> <span class="hljs-keyword">return</span> fib(n-<span class="hljs-number">1</span>) + fib(n-<span class="hljs-number">2</span>)</pre></div><p id="038d">Here, <code>@cache</code> computes and stores each value of <code>n</code> and <code>fib(n)</code> such that each <code>fib(n)</code> is computed only once. For instance, <code>fib(5)</code> is computed only one time, and stored in some store.</p><p id="3213">And if we need <code>fib(5)</code> again later elsewhere, we do not compute it again. Instead, we simply retrieve its value from the store, thus making things a lot faster as we do not need to recompute stuff again and again.</p><h1 id="c57f">6) @staticmethod and @classmethod</h1><div id="855d"><pre><span class="hljs-keyword">class</span> <span class="hljs-title class_">Dog</span>: all_dog_names = []
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name, age</span>):
self.name = name
self.age = age
self.__class__.all_dog_names.append(name)
<span class="hljs-meta"> @classmethod</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_all_dog_names</span>(<span class="hljs-params">cls</span>): <span class="hljs-keyword">return</span> cls.all_dog_names
<span class="hljs-meta"> @staticmethod</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">x, y</span>): <span class="hljs-keyword">return</span> x + y
rocky = Dog(<span class="hljs-string">'rocky'</span>, <span class="hljs-number">4</span>) fifi = Dog(<span class="hljs-string">'fifi'</span>, <span class="hljs-number">4</span>) baaron = Dog(<span class="hljs-string">'baaron'</span>, <span class="hljs-number">4</span>)
<span class="hljs-built_in">print</span>(Dog.get_all_dog_names())
<span class="hljs-comment"># ['rocky', 'fifi', 'baaron']</span></pre></div><p id="9210">Using the built-in <code>classmethod</code> and <code>staticmethod</code> decorators, we can create class methods and static methods.</p><ul><li>class methods belong to the class itself (think <code>Dog</code> rather than <code>rocky</code>) only have access to class attributes, and not instance attributes.</li><li>static methods have access to neither class nor instance attributes.</li></ul><h1 id="0e60">7) @wraps</h1><div id="f9f0"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>): <span class="hljs-string">'''says hello to someone'''</span> <span class="hljs-keyword">return</span> <span class="hljs-string">f'hello <span class="hljs-subst">{name}</span>'</span>
<span class="hljs-built_in">print</span>(greet.name) <span class="hljs-comment"># greet</span> <span class="hljs-built_in">print</span>(greet.doc) <span class="hljs-comment"># says hello to someone</span></pre></div><p id="1495">^ we can check function metadata using methods like <code>function.name</code> and <code>function.doc</code></p><div id="25dc"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">add_exclamation</span>(<span class="hljs-params">func</span>): <span class="hljs-keyword">def</span> <span class="hljs-title function_">wrapper</span>(<span class="hljs-params">name</span>): <span class="hljs-keyword">return</span> func(name) + <span class="hljs-string">'!'</span> <span class="hljs-keyword">return</span> wrapper
<span class="hljs-meta">@add_exclamation</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>): <span class="hljs-string">'''says 'hello' to someone'''</span> <span class="hljs-keyword">return</span> <span class="hljs-string">f'hello <span class="hljs-subst">{name}</span>'</span>
<span class="hljs-built_in">print</span>(greet.name) <span class="hljs-comment"># wrapper</span> <span class="hljs-built_in">print</span>(greet.doc) <span class="hljs-comment"># None</span></pre></div><p id="f02e">However, we can decorate our <code>greet</code> function, our metadata becomes messed up. This is because we’re actually doing <code>greet = add_exclamation(greet)</code>, and reassigning the variable <code>greet</code> to the <code>wrapper</code> function inside of <code>add_exclamation</code>.</p><div id="fd63"><pre><span class="hljs-keyword">from</span> functools <span class="hljs-keyword">import</span> wraps
<span class="hljs-keyword">def</span> <span class="hljs-title function_">add_exclamation</span>(<span class="hljs-params">func</span>): <span class="hljs-meta"> @wraps(<span class="hljs-params">func</span>)</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">wrapper</span>(<span class="hljs-params">name</span>): <span class="hljs-keyword">return</span> func(name) + <span class="hljs-string">'!'</span> <span class="hljs-keyword">return</span> wrapper
<span class="hljs-meta">@add_exclamation</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>): <span class="hljs-string">'''says 'hello' to someone'''</span> <span class="hljs-keyword">return</span> <span class="hljs-string">f'hello <span class="hljs-subst">{name}</span>'</span>
<span class="hljs-built_in">print</span>(greet.name) <span class="hljs-built_in">print</span>(greet.doc)</pre></div><p id="875d">^ by using <code>@wraps(func)</code>, we can stop our function metadata from being screwed up</p><p id="8a40">Here, <code>@wraps</code> helps us to preserve our function’s metadata even when it is being decorated.</p><h1 id="dd0f">Conclusion</h1><p id="2c39">Hope this was clear and easy to understand.</p><h1 id="6108">Some Final words</h1><p id="1103"><i>If this story was helpful and you wish to show a little support, you could:</i></p><ol><li><i>Clap 50 times for this story</i></li><li><i>Leave a comment telling me what you think</i></li><li><i>Highlight the parts in this story that resonate with you</i></li></ol><p id="1ab3"><i>These actions really really help me out, and are much appreciated!</i></p><p id="5e54"><b>Ebooks I’ve Written: <a href="https://zlliu.co/books">https://zlliu.co/ebooks</a></b></p><p id="5555"><b>LinkedIn: <a href="https://www.linkedin.com/in/zlliu/">https://www.linkedin.com/in/zlliu/</a></b></p><div id="e13f" class="link-block"> <a href="https://zlliu.medium.com/subscribe"> <div> <div> <h2>Get an email whenever Liu Zuo Lin publishes.</h2> <div><h3>Get an email whenever Liu Zuo Lin publishes. By signing up, you will create a Medium account if you don't already have…</h3></div> <div><p>zlliu.medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*-TZOYEq7qOp_K6CQ)"></div> </div> </div> </a> </div></article></body>

Day 77 of experimenting with video content
I actually never knew about this until the first year of work (5 years into learning Python).
@your_decorator
def test():
pass^ this is a function test being decorated by the decorator your_decorator
def test():
pass
test = your_decorator(test)^ this is the exact same as the earlier block of code.
@seomthing('test')
def test():
pass^ you’ve probably seen special decorators like this.
def test():
pass
test = something('test')(test)^ this is the exact same as the earlier code block.
Here, the function call something('test') itself returns a decorator, which our function test is passed into. So here, something is an advanced decorator which returns a function which returns a function.
def add_symbol(symbol):
def decorator(func):
def wrapper(name):
return func(name) + symbol
return wrapper
return decorator
@add_symbol('!')
def greet(name):
return f'hello {name}'
print(greet('tom')) # hello tom!^ an example of an advanced decorator.
Advanced decorators can be a tad confusing with the double nested function. Instead, we can use a class as a decorator such that we do not need to deal with the double nested function.
def add_symbol(symbol):
def decorator(func):
def wrapper(name):
return func(name) + symbol
return wrapper
return decorator^ an advanced decorator
class add_symbol():
def __init__(self, symbol):
self.symbol = symbol
def __call__(self, func):
def wrapper(name):
return func(name) + self.symbol
return wrapper^ rewriting the advanced decorator add_symbol using a class rather than a function.
And this is possible due to the __call__ magic method, which defines object behaviour when we call the object like we would a function.
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s:%(levelname)s:%(asctime)s:%(message)s',
filename='app.log',
)
logger = logging.getLogger('my_logger')
def log(message):
def decorator(func):
def wrapper(*args, **kwargs):
logger.info(message)
return func(*args, **kwargs)
return wrapper
return decorator
@log('apple orange pear')
def greet(name):
return f'hello {name}'
print(greet('tom'))Here, whenever we call the greet function, we automatically log the message 'apple orange pear'. We no longer need to manually type logger.info(whatever) every time we call the greet function.
Let’s write a recursive function fib(n) that returns the nth fibonacci number.
The fibonacci number sequence starts with [0, 1], and every subsequent number is the sum of the previous 2 numbers. It goes [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...]
def fib(n):
if n == 1:
return 0
if n == 2:
return 1
return fib(n-1) + fib(n-2)^ here’s a recursive implementation of fib(n). Note that if we call fib(5), we have quite a number of function calls.
f(5) = f(3) + f(4)
= [f(1) + f(2)] + [f(2) + f(3)]
= [f(1) + f(2)] + [f(2) + [f(1) + f(2)]]
= [0+1] + [1 + [0+1]]
= 3Here, notice that f(3) is computed twice, which isn’t very efficient. And this problem gets worse the higher our n becomes. Take f(7):
f(7) = f(5) + f(6)
= [f(3) + f(4)] + [f(4) + f(5)]
= [f(1) + f(2)] + [f(2) + f(3)] + [f(2) + f(3)] + [f(3) + f(4)]
= f(1)+f(2)+f(2)+[f(1)+f(2)]+f(2)+[f(1)+f(2)]+[f(1)+f(2)]+[f(2)+f(3)]
= f(1)+f(2)+f(2)+f(1)+f(2)+f(2)+f(1)+f(2)+f(1)+f(2)+f(2)+[f(1)+f(2)]
= 0+1+1+0+1+1+0+1+0+1+1+0+1
= 8if n goes any higher, i don’t really wanna type it out. But you can see that there are many many repeat computations in this process. But @cache can get rid of these repeat computations.
from functools import cache
@cache
def fib(n):
if n == 1:
return 0
if n == 2:
return 1
return fib(n-1) + fib(n-2)Here, @cache computes and stores each value of n and fib(n) such that each fib(n) is computed only once. For instance, fib(5) is computed only one time, and stored in some store.
And if we need fib(5) again later elsewhere, we do not compute it again. Instead, we simply retrieve its value from the store, thus making things a lot faster as we do not need to recompute stuff again and again.
class Dog:
all_dog_names = []
def __init__(self, name, age):
self.name = name
self.age = age
self.__class__.all_dog_names.append(name)
@classmethod
def get_all_dog_names(cls):
return cls.all_dog_names
@staticmethod
def add(x, y):
return x + y
rocky = Dog('rocky', 4)
fifi = Dog('fifi', 4)
baaron = Dog('baaron', 4)
print(Dog.get_all_dog_names())
# ['rocky', 'fifi', 'baaron']Using the built-in classmethod and staticmethod decorators, we can create class methods and static methods.
Dog rather than rocky) only have access to class attributes, and not instance attributes.def greet(name):
'''says hello to someone'''
return f'hello {name}'
print(greet.__name__) # greet
print(greet.__doc__) # says hello to someone^ we can check function metadata using methods like function.__name__ and function.__doc__
def add_exclamation(func):
def wrapper(name):
return func(name) + '!'
return wrapper
@add_exclamation
def greet(name):
'''says 'hello' to someone'''
return f'hello {name}'
print(greet.__name__) # wrapper
print(greet.__doc__) # NoneHowever, we can decorate our greet function, our metadata becomes messed up. This is because we’re actually doing greet = add_exclamation(greet), and reassigning the variable greet to the wrapper function inside of add_exclamation.
from functools import wraps
def add_exclamation(func):
@wraps(func)
def wrapper(name):
return func(name) + '!'
return wrapper
@add_exclamation
def greet(name):
'''says 'hello' to someone'''
return f'hello {name}'
print(greet.__name__)
print(greet.__doc__)^ by using @wraps(func), we can stop our function metadata from being screwed up
Here, @wraps helps us to preserve our function’s metadata even when it is being decorated.
Hope this was clear and easy to understand.
If this story was helpful and you wish to show a little support, you could:
These actions really really help me out, and are much appreciated!
Ebooks I’ve Written: https://zlliu.co/ebooks
LinkedIn: https://www.linkedin.com/in/zlliu/