python 装饰器

2021-09-06 11:21:32 浏览数 (1)

文章目录

    • 1. 装饰器在导入的时候就会执行
    • 2. functools.wraps 装饰器,保持 被装饰的函数的 `__name__` 的值不变
    • 3. functools.lru_cache 实现备忘录
    • 4. functools.singledispatch 处理多个不同的输入类型
    • 5. 堆叠装饰器
    • 6. 参数化装饰器

learn from 《流畅的python》

代码语言:javascript复制
def deco(func):
    def inner():
        print("running inner()")

    return inner  # deco 返回 inner 函数对象


@deco
def target():
    print('running target()')


target()  # running inner() 调用被装饰的 target,运行的是 inner
print(target)  # <function deco.<locals>.inner at 0x000001B66CBBD3A0>
# target 是 inner 的引用

1. 装饰器在导入的时候就会执行

代码语言:javascript复制
registry = []


def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func


@register
def f1():
    print('running f1()')


@register
def f2():
    print('running f2()')


def f3():
    print('running f3()')


def main():
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()


if __name__ == '__main__':
    main()
    # running register(<function f1 at 0x000002770981D550>) # 装饰器导入时立即运行
    # running register(<function f2 at 0x000002770981D430>) # 装饰器导入时立即运行
    # running main()
    # registry -> [<function f1 at 0x000002770981D550>, <function f2 at 0x000002770981D430>]
    # running f1()
    # running f2()
    # running f3()
    # 可以看见 f1(), f2() 还没有运行,因为被装饰了,已经被加到 list 内了
  • 简单例子
代码语言:javascript复制
# 装饰器示例
# clockdeco.py  输出函数运行时间
import time
def clock(func):
    def clocked(*args): # 内部函数 clocked
        t0 = time.perf_counter()
        res = func(*args) # clocked的闭包中包含自由变量 func
        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, res))
        # 打印 时间, 函数名, 参数名, 函数结果
        return res
    return clocked # 返回内部函数,取代被装饰的函数
代码语言:javascript复制
import time
from clockdeco import clock


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


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


testFunc(.123)
fact(6)

输出:

代码语言:javascript复制
[0.13175840s] testFunc(0.123) -> None
[0.00000070s] fact(1) -> 1
[0.00002130s] fact(2) -> 2
[0.00004540s] fact(3) -> 6
[0.00005840s] fact(4) -> 24
[0.00007230s] fact(5) -> 120
[0.00009220s] fact(6) -> 720

装饰器的 典型 行为: 把 被装饰的函数 替换成 新函数,二者接受相同 的参数,而且(通常)返回被装饰的函数 本该返回的值,同时还会做些 额外操作

代码语言:javascript复制
print(fact.__name__)  # clocked , 上面例子的装饰器有缺点
# 不支持关键参数
# 被装饰函数的 __name__, __doc__ 属性被遮盖

2. functools.wraps 装饰器,保持 被装饰的函数的 __name__ 的值不变

  • 加入 functools.wraps 装饰器,保持 被装饰的函数的 __name__ 的值不变
代码语言:javascript复制
# 装饰器示例
# clockdeco2.py  输出函数运行时间
import time
import functools


def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):  # 内部函数 clocked
        t0 = time.perf_counter()
        res = func(*args, **kwargs)  # clocked的闭包中包含自由变量 func
        elapsed = time.perf_counter() - 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, res))
        # 打印 时间, 函数名, 参数名, 函数结果
        return res

    return clocked  # 返回内部函数,取代被装饰的函数
代码语言:javascript复制
from clockdeco2 import clock

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

fact(6)
print(fact.__name__)  # fact
# @functools.wraps 起到了作用, 被装饰的 fact 函数 名没有被遮盖

3. functools.lru_cache 实现备忘录

实现一个斐波那契数计算

代码语言:javascript复制
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) fibonacci(n-1)
print(fibonacci(6))

输出:

代码语言:javascript复制
[0.00000030s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00001400s] fibonacci(2) -> 1
[0.00000030s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00000020s] fibonacci(1) -> 1
[0.00001310s] fibonacci(2) -> 1
[0.00002570s] fibonacci(3) -> 2
[0.00005320s] fibonacci(4) -> 3
[0.00000030s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001260s] fibonacci(2) -> 1
[0.00002550s] fibonacci(3) -> 2
[0.00000020s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001300s] fibonacci(2) -> 1
[0.00000020s] fibonacci(1) -> 1
[0.00000080s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001320s] fibonacci(2) -> 1
[0.00002560s] fibonacci(3) -> 2
[0.00005080s] fibonacci(4) -> 3
[0.00008840s] fibonacci(5) -> 5
[0.00015550s] fibonacci(6) -> 8
8

可以发现有很多重复计算

代码语言:javascript复制
@functools.lru_cache()  # () 可接收参数
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) fibonacci(n-1)
print(fibonacci(6))

输出:(无重复计算,时间也快了很多)

代码语言:javascript复制
[0.00000030s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001600s] fibonacci(2) -> 1
[0.00000060s] fibonacci(3) -> 2
[0.00003130s] fibonacci(4) -> 3
[0.00000050s] fibonacci(5) -> 5
[0.00004570s] fibonacci(6) -> 8
8
  • functools.lru_cache(maxsize=128, typed=False)maxsize 指定存储多少个调用结果,满了后,删除旧的,推荐设置为2的幂
  • typed 设为True把不同的参数类型分开保存(区分 1, 1.0)
  • lru_cache 使用的是字典存储,key 是 传入的定位参数和关键字参数,所以被装饰函数的所有参数必须是可散列的

4. functools.singledispatch 处理多个不同的输入类型

代码语言:javascript复制
import numbers


@functools.singledispatch
def testFunc(val):
    return "{}".format(val)


@testFunc.register(str) # 处理字符串输入
def _(val):
    return val   " add str"


@testFunc.register(numbers.Integral) # 处理int输出
def _(val):
    return str(val   100)


@testFunc.register(tuple) # 处理 tuple 输入
def _(val):
    return ",".join(testFunc(v) for v in val)


print(testFunc(5))
print(testFunc("hello"))
print(testFunc((5, "hello", (6, 7))))

# 输出
# 105
# hello add str
# 105,hello add str,106,107

5. 堆叠装饰器

代码语言:javascript复制
@d1 
@d2 
def f(): 
	print('f')

等价于

代码语言:javascript复制
def f(): 
	print('f') 
	
f = d1(d2(f))

6. 参数化装饰器

  • 例子1:接受参数,是否注册函数
代码语言:javascript复制
print("running start ")
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()')

print(registry)
f1()
f2()
f3()

输出:

代码语言:javascript复制
running start 
running register(active=False)->decorate(<function f1 at 0x0000014464380A60>)
running register(active=True)->decorate(<function f2 at 0x0000014461A6D3A0>)
{<function f2 at 0x0000014461A6D3A0>}
running f1()
running f2()
running f3()
  • 例子2,接受字符串格式,打印不同的输出
代码语言:javascript复制
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())) # **locals() 在 fmt 中引用 clocked 的局部变量
            return _result # clocked 会取代被装饰的函数,它应该返回被装饰的函数返回的值
        return clocked # decorate 返回 clocked
    return decorate # clock 返回 decorate


@clock() # 不传参数,使用默认的
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)
# [0.13069201s] snooze(0.123) -> None
# [0.13592529s] snooze(0.123) -> None
# [0.13488460s] snooze(0.123) -> None

@clock('{name}: {elapsed}s')
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)
# snooze: 0.134782075881958s
# snooze: 0.1345205307006836s
# snooze: 0.13508963584899902s

@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)
# snooze(0.123) dt = 0.134s
# snooze(0.123) dt = 0.135s
# snooze(0.123) dt = 0.135s

0 人点赞