记录博客 ZH-BLOG

Python functools 模块中有用的装饰器

时间:2018-08-08 23:42:00分类:python

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

maxsize 参数指定存储多少个调用的结果。 缓存满了之后, 旧的结果会被扔掉, 腾出空间。 为了得到最佳性能, maxsize 应该设为 2 的幂,若设为 None,则可无限增长,没有限制。 typed 参数如果设为 True, 把不同参数类型得到的结果分开保存, 如:通常认为相等的浮点数和整数参数(如 1 和 1.0) 区分开。

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('[%.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result
    return clocked

from clockdeco import clock


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

if __name__ == '__main__':
    print(fibonacci(6))

[0.00000106s] fibonacci(0) -> 0
[0.00000283s] fibonacci(1) -> 1
[0.01172350s] fibonacci(2) -> 1
[0.00000142s] fibonacci(1) -> 1
[0.00000177s] fibonacci(0) -> 0
[0.00000248s] fibonacci(1) -> 1
[0.00822842s] fibonacci(2) -> 1
[0.01618506s] fibonacci(3) -> 2
[0.03584855s] fibonacci(4) -> 3
[0.00000106s] fibonacci(1) -> 1
[0.00000142s] fibonacci(0) -> 0
[0.00000283s] fibonacci(1) -> 1
[0.00792443s] fibonacci(2) -> 1
[0.01656939s] fibonacci(3) -> 2
[0.00000142s] fibonacci(0) -> 0
[0.00000248s] fibonacci(1) -> 1
[0.00798282s] fibonacci(2) -> 1
[0.00000142s] fibonacci(1) -> 1
[0.00000177s] fibonacci(0) -> 0
[0.00000248s] fibonacci(1) -> 1
[0.00798601s] fibonacci(2) -> 1
[0.01679093s] fibonacci(3) -> 2
[0.03273356s] fibonacci(4) -> 3
[0.05797551s] fibonacci(5) -> 5
[0.10186775s] fibonacci(6) -> 8
8

使用 functools.lru_cache 缓存结果

>>> @functools.lru_cache()
def t(arg):
	print('run...')
	return arg

>>> [t(i) for i in (1,2,3,3,2,1)]
run...
run...
run...
[1, 2, 3, 3, 2, 1]

from clockdeco import clock
import functools


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

if __name__ == '__main__':
    print(fibonacci(6))

[0.00000142s] fibonacci(0) -> 0
[0.00000248s] fibonacci(1) -> 1
[0.01075913s] fibonacci(2) -> 1
[0.00000531s] fibonacci(3) -> 2
[0.02018160s] fibonacci(4) -> 3
[0.00000531s] fibonacci(5) -> 5
[0.02896599s] fibonacci(6) -> 8
8

2. functools.singledispatch

functools.singledispatch 装饰器可以把整体方案 拆分成多个模块, 甚至可以为你无法修改的类提供专门的函数。 使用 @singledispatch 装饰的普通函数会变成泛函数(generic function) : 根据第一个参数的类型, 以不同方式执行相同操作的一组函数。

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


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


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


@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>{}</li>\n</ul>'.format(inner)


>>> from sp import htmlize
>>> htmlize({1,2,3})
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre>&lt;built-in function abs&gt;</pre>'
>>> htmlize('Hi \n i am tom')
'<p>Hi <br>\n i am tom</p>'
>>> htmlize(42)
'<pre>42 (0x2a)</pre>'
>>> htmlize(['alpha', 66, {3,2,1}])
'<ul>\n<li><p>alpha</p></li>\n<li><pre>66 (0x42)</pre></li>\n<li><pre>{1, 2, 3}</pre></li>\n</ul>'
>>> print(htmlize(['alpha', 66, {3,2,1}]))
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>


3. 参数化的注册装饰器

registry = set()
def register(active=True):
def decorate(func):
print('running register(active=%s)->decorate(%s)'
% (active, func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('running f1()')
@register()
def f2():
print('running f2()')
def f3():
print('running f3()')

>>> import registration_param
running register(active=False)->decorate(<function f1 at 0x10063c1e0>)
running register(active=True)->decorate(<function f2 at 0x10063c268>)
>>> registration_param.registry
{<function f2 at 0x10063c268>}


4. 参数化 clock 装饰器

import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT): 
    def decorate(func): 
        def clocked(*_args): 
            t0 = time.time()
            _result = func(*_args) 
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args) result = repr(_result) 
            print(fmt.format(**locals())) 
            return _result 
        return clocked 
    return decorate 

@clock()
def snooze(seconds):
    time.sleep(seconds)

snooze(.123)
[0.12412500s] snooze(0.123) -> None

@clock('{name}: {elapsed}s')
def snooze(seconds):
    time.sleep(seconds)

snooze(.123)
snooze: 0.12414693832397461s

@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
    time.sleep(seconds)

snooze(.123)
snooze(0.123) dt=0.124s