avatarLiu Zuo Lin

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>

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

Day 77 of experimenting with video content

1) Decorator Syntax Equivalent

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.

2) Advanced decorators

@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.

3) Classes can be decorators too

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.

4) We can use decorators for admin stuff eg. logging/timing stuff

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.

5) @cache

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]]
     = 3

Here, 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
     = 8

if 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.

6) @staticmethod and @classmethod

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.

  • class methods belong to the class itself (think Dog rather than rocky) only have access to class attributes, and not instance attributes.
  • static methods have access to neither class nor instance attributes.

7) @wraps

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__)     # None

However, 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.

Conclusion

Hope this was clear and easy to understand.

Some Final words

If this story was helpful and you wish to show a little support, you could:

  1. Clap 50 times for this story
  2. Leave a comment telling me what you think
  3. Highlight the parts in this story that resonate with you

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/

Python
Python Programming
Python3
Programming
Coding
Recommended from ReadMedium