Chapter 7 function decorator

1, Concept
1. Decorator is a callable object whose parameter is another function (decorated function). Generally, the function of decorator is to add additional functions to an existing function.
2, Python built-in decorator
Python has some built-in decorators. There are wraps and LRU in functools module_ Cache and singledispatch are three function decorators. In addition, there are three decorators for decorating methods: property, classmethod and static method.
1.wraps decorator
wraps decorator can correctly leave some attributes of the decorated function in the decorated function.
Example: clock is a simple decorator for calculating function runtime.

import time

def clock(func):
    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - 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

Note: r% is a universal formatter, which will print the parameters as they are, with type information.
Using the clock decorator:

import time
from example7_15 import clock

def snooze(seconds):

def factorial(n):
    return 1 if n < 2 else n*factorial(n-1)

if __name__ == '__main__':
[0.13834610s] snooze(0.123) -> None
clocked None
clocked None
[0.00000040s] factorial(1) -> 1

The clock decorator can achieve the expected effect and correctly display the execution time of the function. However, some properties of the decorated function are modified, and keyword parameter passing is not supported. This is not what we want to see.
Improvement: clock uses wrap decorator

import time
import functools

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

The improved decorator makes up for the above shortcomings.
2.lru_cache decorator
lru_ The cache decorator implements the memo function. This is an optimization technique. It stores the results of the function to avoid repeated calculations by passing in the same parameters. LRU is the abbreviation of lease recently used. Using this decorator in recursive functions has a very obvious speed improvement effect.
Example: generate n Fibonacci numbers

from example7_17 import clock

def f(n):
    return f(n-2) + f(n-1) if n >2 else 1
if __name__ == '__main__':
Insert the code slice here[0.00000000s] f(2) -> 1
[0.00000000s] f(1) -> 1
[0.00000000s] f(2) -> 1
[0.00000000s] f(3) -> 2
[0.00000000s] f(4) -> 3
[0.00000000s] f(1) -> 1
[0.00000000s] f(2) -> 1
[0.19500208s] f(19) -> 4181
[0.25860739s] f(20) -> 6765

Using the previous clock decorator for timing, you can see that ordinary recursion can correctly output results, but f is repeatedly calculated many times, such as f(1),f(2)
Using LRU_ After the cache is decorated, the previously calculated results of f(1),f(2)... Will be remembered.

import functools
from example7_17 import clock

def f(n):
    return f(n-2)+f(n-1) if n>2 else 1

if __name__ == '__main__':
[0.00000000s] f(2) -> 1
[0.00000000s] f(1) -> 1
[0.00000000s] f(3) -> 2
[0.00000000s] f(4) -> 3
[0.00000000s] f(5) -> 5
[0.00000000s] f(6) -> 8
[0.00000000s] f(7) -> 13
[0.00000000s] f(8) -> 21
[0.00000000s] f(9) -> 34
[0.00000000s] f(10) -> 55
[0.00000000s] f(11) -> 89
[0.00000000s] f(12) -> 144
[0.00000000s] f(13) -> 233
[0.00000000s] f(14) -> 377
[0.00000000s] f(15) -> 610
[0.00000000s] f(16) -> 987
[0.00000000s] f(17) -> 1597
[0.00000000s] f(18) -> 2584
[0.00000000s] f(19) -> 4181
[0.00000000s] f(20) -> 6765

Each f is calculated only once, and the efficiency is significantly improved.
3. Single dispatch decorator
The singledispatch decorator can create a generic function that binds multiple functions together.

Requirements: implement a function to generate HTML. This function adds different tags to objects according to different incoming objects.

from functools import singledispatch
from collections import abc
import numbers
import html

def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

def _(text):
    content = html.escape(text).replace('\n','<br>\n')
    return '<p>{}</p>'.format(content)
def _(n):
    return '<pre>{0} (0x{0:x})</pre>'.format(n)

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(htmlize('heimlich & Co.\n- a game'))
print(htmlize(['alpha', 66, {3, 2, 1}]))

You can see that the singledispatch decorator generates an HTML. Register decorator, which registers special functions as universal functions.
(1)_ It is used to write meaningless temporary variables in Python, which is no different from ordinary variables;
(2) Special functions try to handle an abstract base class (numbers.Integral, abc.MutableSequence) rather than a specific class (int, list).
(3) The stacked decorators are executed from the bottom. For example, yes

def f():

Then it is equivalent to executing f = decorator1(decorator2(f)), which is similar to the compound function in mathematics.

Tags: Python Functional Programming

Posted on Fri, 26 Nov 2021 18:24:16 -0500 by POGRAN