第7章 函数装饰器

一、概念
1.装饰器是一个可调用对象,其参数是另一个函数(被装饰的函数)。一般装饰器的功能都是给一个已有的函数增加额外的功能。
二、Python内置的装饰器
Python中有一些内置的装饰器。其中functools模块中有wraps、lru_cache和singledispatch三个函数装饰器。此外还有property、classmethod以及staticmethod三个用于装饰方法的装饰器。
1.wraps装饰器
wraps装饰器能够把被装饰的函数的一些属性正确的留在装饰后的函数中。
示例:clock是一个用于计算函数运行时间的简单装饰器。

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

注:r%是一个万能的格式符,会把参数原样打印,带有类型信息。
使用clock装饰器:

import time
from example7_15 import clock



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

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

if __name__ == '__main__':
    snooze(0.123)
    print(snooze.__name__,snooze.__doc__)
    print('*********'*5)
    print(factorial.__name__,factorial.__doc__)
    factorial(30)
[0.13834610s] snooze(0.123) -> None
clocked None
*********************************************
clocked None
[0.00000040s] factorial(1) -> 1
...

clock装饰器能够达到预期的效果,正确的显示函数的执行时间。但是,却修改掉了被装饰函数的一些属性,并且不能支持关键字传参。这不是我们希望看到的。
改进:clock使用wrap装饰器

import time
import functools

def clock(func):
    @functools.wraps(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

改进之后的装饰器弥补了上述的缺点。
2.lru_cache装饰器
lru_cache装饰器实现了备忘功能。这是一项优化技术,他把函数的结果储存起来,避免传入相同参数是进行重复的计算。lru是lease recently used的缩写。在递归函数中使用这个装饰器速度提升效果非常明显。
示例:生成n个斐波那契数

from example7_17 import clock

@clock
def f(n):
    return f(n-2) + f(n-1) if n >2 else 1
if __name__ == '__main__':
    print(f(20))
在这里插入代码片[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
6765

使用前面的clock装饰器进行计时,可以看到普通的递归可以正确输出结果,但是有很多次的f都重复计算了,比如f(1),f(2)…
使用lru_cache装饰后,会把之前计算的f(1),f(2)…结果给记住。

import functools
from example7_17 import clock

@functools.lru_cache()
@clock
def f(n):
    return f(n-2)+f(n-1) if n>2 else 1

if __name__ == '__main__':
    print(f(20))
[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
6765

每个f只会计算一次,效率明显提升了。
3.singledispatch装饰器
singledispatch装饰器可以实现创建一个泛函数,这个泛函数把多个函数绑定在一起。

需求:实现一个生成HTML的函数,这个函数根据传入的对象不同给对象加上不同的标签。
示例:

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

@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>{}</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>' + inner + '</li>\n</ul>'

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

可以看到singledispatch装饰器生成了一个htmlize.register装饰器,这个装饰器将专门的函数注册为泛函数。
注:
(1)_是Python中用于无意义的临时变量的写法,与普通的变量没有区别;
(2)专门函数尽量用来处理一个抽象基类(numbers.Integral、abc.MutableSequence),而不是具体的类(int、list)。
(3)叠放的装饰器从最底下开始执行。例如有

@decorator1
@decorator2
def f():
	pass

那么相当于执行了f = decorator1(decorator2(f)),类似于数学中的复合函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值