Python funtools 中的 lru

Python funtools 中的 lru,第1张

Python funtools 中的 lru

摘自流畅的Python 第七章 函数装饰器和闭包

使用functools.lru_cache 做备忘

functools.lru_cache 是非常使用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数是重复计算

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

我们使用斐波那契数列作为例子 观察
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)

部分输出如下

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


浪费时间的地方很明显:fibonacci(1) 调用了8次,fibonacci(5)调用了5次…
现在我们在代码中添加lru_cache

fibo_demo_lru.py

import functools
import time

from clockdeco import clock


@functools.lru_cache()  # 注意,必须像常规函数那样调用 lru_cache。这一行中有一对括 号:@functools.lru_cache()。这么做的原因是,lru_cache 可以 接受配置参数,稍后说明。
@clock  # 这里叠放了装饰器:@lru_cache() 应用到 @clock 返回的函数上。这样一来,执行时间减半了,而且 n 的每个值只调用一次函数:
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)

部分输出如下

[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

由此可以看出使用lru_cache 优化的斐波那契 在计算的耗时方面极大程度的优化了

特别要注意,lru_cache可以使用两个可选的参数来配置。它的签名是

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

maxsize参数指定存储多少个调用的结果。缓存慢了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize应该设为2的幂。
type参数如果设为True,把不同参数类型得到的结果分开保存,即把通常人为相等的浮点数和整数参数(如1和1.0)区分开。顺便说一下,因为lru_cache使用字典存储结果,而且键根据调用是传入的定位参数和关键字参数创建,所以被lru_cache装饰的函数,它的所有参数都必须是可散列的

单分派泛函数 singledispatch(重载)

假设我们在开发一个调试Web应用的工具,我们想生成HTML,显示不同类型的Python对象。我们可能会编写这样的函数

import html
def htmlize(obj):
    # repr() 函数将对象转化为供解释器读取的形式
    content = html.escape(repr(obj))
    return '
{}
'.format(content) print(htmlize({1,2,3}))

这个函数使用与任何Python类型,但是现在我们想做个拓展,让它使用特别的方式显示某些类型

str: 把内部的换行符替换为’
n’; 不使用

,而是使用

int: 以十进制和十六进制显示数字list: 输出一个HTML列表,根据各个元素的类型进行格式化

{1, 2, 3}

因为Python不支持重载方法或函数,所以我们不能使用不同的签名定义htmlize变体,也无法使用不同的方式处理不同的数据类型

generic.py

from functools import singledispatch
from collections import abc
import numbers
import html
# @singledispatch 标记处理 object 类型的基函数。
@singledispatch
def htmlize(obj):
    content = html.escape(repr(obj))
    return '
{}
'.format(content) # 各个专门函数使用 @«base_function».register(«type») 装饰。 @htmlize.register(str) def _(text): content = html.escape(text).replace('n', '
n') return '

{0}

'.format(content) @htmlize.register(numbers.Integral) def _(n): return '
{0} (0x{0:x})
'.format(n) # 可以叠放多个 register 装饰器,让同一个函数支持不同类型。 @htmlize.register(tuple) @htmlize.register(abc.MutableSequence) def _(seq): inner = 'n'.join(htmlize(item) for item in seq) return '

n' + inner + 'n' 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}]))

输出如下

{1, 2, 3}
----------------------------------------
<built-in function abs>
----------------------------------------

Heimlich & Co.
- a game

----------------------------------------
42 (0x2a)
----------------------------------------

alpha

66 (0x42)
{1, 2, 3}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5721497.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存