前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 装饰器

python 装饰器

作者头像
Michael阿明
发布2021-09-06 11:21:32
3770
发布2021-09-06 11:21:32
举报
文章被收录于专栏:Michael阿明学习之路

文章目录

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
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/07/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1. 装饰器在导入的时候就会执行
  • 2. functools.wraps 装饰器,保持 被装饰的函数的 __name__ 的值不变
  • 3. functools.lru_cache 实现备忘录
  • 4. functools.singledispatch 处理多个不同的输入类型
  • 5. 堆叠装饰器
  • 6. 参数化装饰器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档