LRU in Python fortuols_ Cache and singledispatch

Posted by beaudoin on Thu, 03 Feb 2022 04:29:36 +0100

From fluent Python Chapter 7 function decorators and closures

Use functools lru_ Cache notes

functools.lru_cache is a very useful decorator. It implements the function of memo. This is an optimization technology, which saves the results of time-consuming functions to avoid double calculation by passing in the same parameters

clockdeco.py

import time

def clock(func):
    def clocked(*args):
        t0 = time.time()
        result = func(*args)
        elapsed = time.time() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result

    return clocked

We use the Fibonacci sequence as an example
fibo_demo.py

from clockdeco import clock
import time

@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__=='__main__':
    start = time.time()
    print(fibonacci(30))
    print(time.time() - start)

Some outputs are as follows

...
[0.92167187s] fibonacci(24) -> 46368
[1.41511607s] fibonacci(25) -> 75025
[2.26029801s] fibonacci(26) -> 121393
[3.61707902s] fibonacci(27) -> 196418
[5.80630493s] fibonacci(28) -> 317811
[9.39297986s] fibonacci(29) -> 514229
[15.82301521s] fibonacci(30) -> 832040
832040
15.82302474975586


The waste of time is obvious: fibonacci(1) called 8 times, fibonacci(5) called 5 times
Now let's add LRU to the code_ cache

fibo_demo_lru.py

import functools
import time

from clockdeco import clock


@functools.lru_cache()  # Note that LRU must be called like a regular function_ cache.  There are a pair of parentheses in this line: @ functools lru_cache().  The reason for this is that lru_cache can accept configuration parameters, which will be described later.
@clock  # There are decorations stacked here: @lru_cache() is applied to the function returned by @ clock. In this way, the execution time is halved, and each value of n calls the function only once:
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)


if __name__ == '__main__':
    start = time.time()
    print(fibonacci(30))
    print(time.time() - start)

Some outputs are as follows

[0.00000119s] fibonacci(25) -> 75025
[0.00016594s] fibonacci(26) -> 121393
[0.00000000s] fibonacci(27) -> 196418
[0.00020003s] fibonacci(28) -> 317811
[0.00000072s] fibonacci(29) -> 514229
[0.00020981s] fibonacci(30) -> 832040
832040
0.00021791458129882812

It can be seen that LRU is used_ The cache optimized Fibonacci greatly optimizes the computing time

Special attention, lru_cache can be configured with two optional parameters. Its signature is

functools.lru_cache(maxsize=128, typed=False)

The maxsize parameter specifies how many call results are stored. When the cache is slow, the old results will be thrown away to make room. For best performance, maxsize should be set to a power of 2.
If the type parameter is set to True, the results obtained by different parameter types will be saved separately, that is, the floating-point numbers and integer parameters (such as 1 and 1.0) that are usually artificially equal will be distinguished. By the way, because LRU_ The cache uses a dictionary to store the results, and the key is created according to the location parameters and keyword parameters passed in by the call, so it is LRU_ All parameters of a cache decorated function must be hashable

Single dispatch universal function singledispatch (overload)

Suppose we are developing a tool for debugging Web applications. We want to generate HTML and display different types of Python objects. We might write such a function

import html
def htmlize(obj):
    # The repr() function converts the object to a form that the interpreter can read
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

print(htmlize({1,2,3}))

This function uses any Python type, but now we want to extend it to show some types in a special way

  • str: replace the internal newline character with '< br > \ n'; Use < p > instead of < pre >
  • int: displays numbers in decimal and hexadecimal
  • List: output an HTML list and format it according to the type of each element
<pre>{1, 2, 3}</pre>

Because Python does not support overloaded methods or functions, we cannot define htmlize variants with different signatures, nor can we handle different data types in different ways

generic.py

from functools import singledispatch
from collections import abc
import numbers
import html
# @The singledispatch tag handles the base function of object type.
@singledispatch
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

# Each special function uses @ « base_function». register(« type ») decoration.
@htmlize.register(str)
def _(text):
    content = html.escape(text).replace('\n', '<br>\n')
    return '<p>{0}</p>'.format(content)

@htmlize.register(numbers.Integral)
def _(n):
    return '<pre>{0} (0x{0:x})</pre>'.format(n)

# Multiple register decorators can be stacked so that the same function supports different types.
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    return '<ul>\n<li>' + inner + '</li>\n</ul>'


print(htmlize({1,2,3}))
print('-'*40)
print(htmlize(abs))
print('-'*40)
print(htmlize('Heimlich & Co.\n- a game'))
print('-'*40)
print(htmlize(42))
print('-'*40)
print(htmlize(['alpha', 66, {3, 2, 1}]))

The output is as follows

<pre>{1, 2, 3}</pre>
----------------------------------------
<pre>&lt;built-in function abs&gt;</pre>
----------------------------------------
<p>Heimlich &amp; Co.<br>
- a game</p>
----------------------------------------
<pre>42 (0x2a)</pre>
----------------------------------------
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>

Topics: Python