avatarOliver S

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4054

Abstract

stance.</p><h1 id="50b7">Functools Decorators</h1><p id="01e7"><code>functools</code> is a very interesting Python package, probably worth another post. Here we’ll discuss one decorator, namely <code>lru_cache</code>.</p><p id="89f7">This decorators follows the principle of <a href="https://en.wikipedia.org/wiki/Memoization">memoization</a>: memoization means caching the results of previous function calls. Let’s have a look at the <a href="https://en.wikipedia.org/wiki/Fibonacci_number">Fibonacci sequence</a> for demonstration. In this, sequence number <code>n</code> is defined by the sum of the numbers <code>n-1</code> and <code>n-2</code>, the first and second number are 1 and 2 by definition — yielding the sequence 1, 2, 3, 5, 8, 13, 21, … We could now compute this recursively, i.e. calculate the n-th number as <code>fibonacci(n)</code> = <code>fibonacci(n-1) + fibonacci(n-2)</code>. The call graph for <code>fibonacci(5)</code> is shown in the picture below: the first number of each node describes <code>n</code>, the second the corresponding n-th Fibonacci number.</p><figure id="1d44"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*88t-yH40A0Qvarf5vJAquQ.png"><figcaption></figcaption></figure><p id="afde">As we can see from this graph, several nodes have the same <code>n</code> value — i.e. correspond to the same function call <code>fibonacci(n)</code> (and with higher <code>n</code> this number just grows). With memoization, after calculating <code>fibonacci(n)</code> once, the output is stored in a cache for previous calls. When implementing above recursive formula as is, i.e. DFS-style, the underscored calls will be read from cache instead.</p><h2 id="edf2">lru_cache</h2><p id="2247">Let’s convert this to Python:</p><div id="e857"><pre><span class="hljs-keyword">import</span> time

<span class="hljs-keyword">def</span> <span class="hljs-title function_">get_fibonacci_number</span>(<span class="hljs-params">n</span>): <span class="hljs-keyword">if</span> n <= <span class="hljs-number">0</span>: <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"n must be non-negative!"</span>) <span class="hljs-keyword">elif</span> n == <span class="hljs-number">1</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">elif</span> n == <span class="hljs-number">2</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">1</span> <span class="hljs-keyword">else</span>: <span class="hljs-keyword">return</span> get_fibonacci_number(n - <span class="hljs-number">1</span>) + get_fibonacci_number(n - <span class="hljs-number">2</span>)

start_time = time.time() <span class="hljs-built_in">print</span>(<span class="hljs-string">f"40th Fibonacci number is: <span class="hljs-subst">{get_fibonacci_number(<span class="hljs-number">40</span>)}</span>, time needd [s]: <span class="hljs-subst">{time.time() - start_time}</span>"</span>)</pre></div><p id="90e9">This code takes around 15s to run.</p><p id="af1f">With a simple change, we employ the <code>lru_cache</code> decorator, cutting down runtime to under 1ms:</p><div id="10f6"><pre><span class="hljs-keyword">from</span> functools <span class="hljs-keyword">import</span> lru_cache <span class="hljs-keyword">import</span> time

<span class="hljs-meta">@lru_cache</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_fibonacci_number</span>(<span class="hljs-params">n</span>): <span class="hljs-keyword">if</span> n <= <span class="hljs-number">0</span>: <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"n must be non-negative!"</span>) <span class="hljs-keyword">elif</span> n == <span class="hljs-number">1</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">elif</span> n == <span class="hljs-number">2</span>: <span class="hljs-keyword">re

Options

turn</span> <span class="hljs-number">1</span> <span class="hljs-keyword">else</span>: <span class="hljs-keyword">return</span> get_fibonacci_number(n - <span class="hljs-number">1</span>) + get_fibonacci_number(n - <span class="hljs-number">2</span>)</pre></div><p id="4afd">“lru cache” stands for least-recently used cache, and by default keeps the last 128 return values in cache. But it can be customized via the <code>maxsize</code> parameter, with a value of <code>None</code> denoting no cache limit (e.g. <code>@lru_cache(maxsize=None)</code>).</p><h1 id="a8f4">Custom Decorators</h1><p id="3178">In this section we will implement one custom decorator — a class decorator describing <a href="https://en.wikipedia.org/wiki/Singleton_pattern">Singletons</a>. This pattern is used to enforce an object can only be instantiated once, which can for example be used for factories.</p><p id="dcc6">Our implementation is based on a dict which manages all available classes, and returns available instances in case these were already constructed:</p><div id="d4e3"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">singleton</span>(<span class="hljs-params">cls</span>): instances = {} <span class="hljs-keyword">def</span> <span class="hljs-title function_">wrapper</span>(<span class="hljs-params">*args, **kwargs</span>): <span class="hljs-keyword">if</span> cls <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> <span class="hljs-symbol">instances:</span> instances[cls] = cls(*args, **kwargs) <span class="hljs-keyword">return</span> instances[cls] <span class="hljs-keyword">return</span> wrapper

<span class="hljs-variable">@singleton</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span>: <span class="hljs-keyword">def</span> <span class="hljs-title function_">init</span>(<span class="hljs-params"><span class="hljs-variable language_">self</span></span>): <span class="hljs-variable language_">self</span>.call_count = <span class="hljs-number">0</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">call</span>(<span class="hljs-params"><span class="hljs-variable language_">self</span></span>):
    print(f<span class="hljs-string">"# calls so far: {self.call_count}"</span>)
    <span class="hljs-variable language_">self</span>.call_count += <span class="hljs-number">1</span>

singleton_instance_one = <span class="hljs-title class_">MyClass</span>() singleton_instance_one.call() singleton_instance_two = <span class="hljs-title class_">MyClass</span>() singleton_instance_two.call()</pre></div><p id="1bcf">We see, that trying to create another class instance returns the already available one. Note that decorators are not the only way for creating singletons in Python, and maybe not even the best ones, depending on your use-case. I’d like to refer to <a href="https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python">this interesting thread on Stackoverflow</a>.</p><p id="e532">This concludes this post about advanced Python decorators. I hope, these come in handy one day for you. If you liked this post, I would be happy about a subscription and about seeing you again another time. Thanks!</p><h2 id="a6f8">More content at PlainEnglish.io.</h2><p id="e1da">Sign up for our <a href="http://newsletter.plainenglish.io/"><b>free weekly newsletter</b></a>. Follow us on <a href="https://twitter.com/inPlainEngHQ"><b>Twitter</b></a>, <a href="https://www.linkedin.com/company/inplainenglish/"><b>LinkedIn</b></a><b>, <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw">YouTube</a>, and</b> <a href="https://discord.gg/GtDtUAvyhW"><b>Discord</b></a><b>.</b></p><h2 id="cd50">Interested in scaling your software startup? Check out Circuit.</h2><p id="cfbc">We offer free expert advice and bespoke solutions to help you build awareness and adoption for your tech product or service.</p></article></body>

Handy Python Decorators

Implementing and Using Advanced Decorators

After introducing the basics of decorators, in this post I’d like to showcase some useful and advanced decorators, stemming from existing libraries or custom implementation.

In particular, we will discuss decorators from the Standard Library (staticmethod, classmethod, dataclass, property), functools (lru_cache) and implement our own singleton decorator.

Photo by James Harrison on Unsplash

Standard Library Decorators

The Python Standard Library contains four incredibly useful decorators: staticmethod, classmethod, dataclas and property. I covered the last two in detail in a previous post, thus here we will just briefly cover the former.

staticmethod and classmethod

Both are very similar: using the respective decorators gives us functions not bound to a specific object / instance of a class. Usually methods are bound to an instance and are called instance methods.

The difference is, that classmethod takes the class itself as first argument, whereas staticmethod does not. Due to this, static methods are usually used for simple helper / utility functions, whereas class methods in situations where one wants to access static class variables, or (very commonly) for creating factory methods.

The following example should demonstrate this:

class MyClass:
    counter = 0

    @staticmethod
    def my_static_method():
        print("Static method called.")

    @classmethod
    def my_class_method(cls):
        print(f"Value of x: {cls.counter}.")
        cls.counter += 1

    @classmethod
    def my_class_factory(cls):
        return cls()

    def my_instance_method(self):
        print("Instance method called.")

MyClass.my_static_method()

MyClass.my_class_method()
MyClass.my_class_method()

my_class_instance = MyClass.my_class_factory()
my_class_instance.my_instance_method()

my_class_method() increments the static variable counter of class, s.t. the second execution prints 1 instead of 0. my_class_factory() is a factory method — this pattern is commonly used to instantiate classes: one passes all needed arguments and data to the factory method, and this is responsible for creating a class instance.

Functools Decorators

functools is a very interesting Python package, probably worth another post. Here we’ll discuss one decorator, namely lru_cache.

This decorators follows the principle of memoization: memoization means caching the results of previous function calls. Let’s have a look at the Fibonacci sequence for demonstration. In this, sequence number n is defined by the sum of the numbers n-1 and n-2, the first and second number are 1 and 2 by definition — yielding the sequence 1, 2, 3, 5, 8, 13, 21, … We could now compute this recursively, i.e. calculate the n-th number as fibonacci(n) = fibonacci(n-1) + fibonacci(n-2). The call graph for fibonacci(5) is shown in the picture below: the first number of each node describes n, the second the corresponding n-th Fibonacci number.

As we can see from this graph, several nodes have the same n value — i.e. correspond to the same function call fibonacci(n) (and with higher n this number just grows). With memoization, after calculating fibonacci(n) once, the output is stored in a cache for previous calls. When implementing above recursive formula as is, i.e. DFS-style, the underscored calls will be read from cache instead.

lru_cache

Let’s convert this to Python:

import time

def get_fibonacci_number(n):
    if n <= 0:
        raise ValueError("n must be non-negative!")
    elif n == 1:
        return 0
    elif n == 2:
        return 1
    else:
        return get_fibonacci_number(n - 1) + get_fibonacci_number(n - 2)


start_time = time.time()
print(f"40th Fibonacci number is: {get_fibonacci_number(40)}, time needd [s]: {time.time() - start_time}")

This code takes around 15s to run.

With a simple change, we employ the lru_cache decorator, cutting down runtime to under 1ms:

from functools import lru_cache
import time

@lru_cache
def get_fibonacci_number(n):
    if n <= 0:
        raise ValueError("n must be non-negative!")
    elif n == 1:
        return 0
    elif n == 2:
        return 1
    else:
        return get_fibonacci_number(n - 1) + get_fibonacci_number(n - 2)

“lru cache” stands for least-recently used cache, and by default keeps the last 128 return values in cache. But it can be customized via the maxsize parameter, with a value of None denoting no cache limit (e.g. @lru_cache(maxsize=None)).

Custom Decorators

In this section we will implement one custom decorator — a class decorator describing Singletons. This pattern is used to enforce an object can only be instantiated once, which can for example be used for factories.

Our implementation is based on a dict which manages all available classes, and returns available instances in case these were already constructed:

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
          instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class MyClass:
    def __init__(self):
        self.call_count = 0

    def call(self):
        print(f"# calls so far: {self.call_count}")
        self.call_count += 1

singleton_instance_one = MyClass()
singleton_instance_one.call()
singleton_instance_two = MyClass()
singleton_instance_two.call()

We see, that trying to create another class instance returns the already available one. Note that decorators are not the only way for creating singletons in Python, and maybe not even the best ones, depending on your use-case. I’d like to refer to this interesting thread on Stackoverflow.

This concludes this post about advanced Python decorators. I hope, these come in handy one day for you. If you liked this post, I would be happy about a subscription and about seeing you again another time. Thanks!

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Interested in scaling your software startup? Check out Circuit.

We offer free expert advice and bespoke solutions to help you build awareness and adoption for your tech product or service.

Python
Python3
Python Decorators
Recommended from ReadMedium