A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 不二晨 金牌黑马   /  2018-11-12 09:05  /  1318 人查看  /  3 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

定义:在不修改一个函数内部代码的情况下,给函数添加扩展功能,即只能在函数之前或者之后添加功能,不能在函数内部添加,常见的应用场景如log日志、权限检验等。1.不带参数的装饰器
  • 无参数无返回值得函数


    # 定义一个装饰器def set_func(func):    def call_func():        print('————在函数之前添加功能——————')        func()        print('————在函数后面添加功能——————')    return call_func# 定义一个函数,并添加装饰器@set_func    # 等价于  func1=set_func(func1)def func1():    print('____func1____')# 调用函数func1()复制代码

  • 代码执行结果————在函数之前添加功能——————____func1____————在函数后面添加功能——————复制代码
2. 有参数有返回值得函数




# 定义装饰器def set_func(func):    def call_func(*args, **kwargs):        print('——————添加功能————————')        return func(*args, **kwargs)    return call_func# 定义一个函数并进行装饰@set_funcdef func1(m):    print('____func1____')    return m# 调用函数f = func1(100)print(f)复制代码
  • 代码执行结果



——————添加功能————————____func1____100复制代码2.带有参数的装饰器



import time# 定义装饰器def set_log(log):    def set_func(func):        # 定义log_dict 字典        log_dict = {1: 'error', 2: 'warning'}        def call_func(*args, **kwargs):            # 打开log.txt文件(没有就创建文件),追加内容            with open('log.txt', 'a', encoding='utf-8') as f:                f.write('%s ---%s---调用了函数%s\n' % (log_dict[log], str(time.ctime()), func.__name__))            return func(*args, **kwargs)        return call_func    return set_func# 定义一个函数并进行装饰@set_log(1)def func1(m):    print('____func1____')    return m# 调用函数f = func1(100)print(f)复制代码
  • 代码执行结果



____func1____100# log.txt 文件内容error ---Fri Jan  5 22:01:24 2018---调用了函数func1复制代码3.一个函数有两个装饰器



# 定义装饰器1def set_log(func):    print('————开始装饰sel_log————')    def call_func():        print('___set_log___')        func()    return call_func# 定义装饰器2def set_func(func):    print('————开始装饰set_func————')    def call_func():        print('___set_func___')        func()    return call_func# 定义函数,并添加装饰器@set_log@set_func    # 等价于  func1=set_func(func1)def func1():    print('____func1____')复制代码
  • 先不调用函数运行代码,会看到如下结果


    ————开始装饰set_func————————开始装饰sel_log————复制代码
  • 调用函数,再看执行结果


    # 调用函数func1()复制代码




# 执行结果————开始装饰set_func————————开始装饰sel_log————___set_log______set_func_______func1____复制代码
  • 解释说明:
  • 装饰器在代码写完后,此时不用调用函数,就会进行装饰
  • 函数装饰时,会先执行离函数最近的装饰器
  • 当调用函数时,函数会先执行离函数最远的装饰器再执行离的近的装饰器,再执行函数本身
4. 一、functools.wraps



import timeimport functools# 定义装饰器def clock(func):    @functools.wraps(func)    # 使用 functools.wraps装饰器把相关的属性从 func复制到 clocked 中,避免遮盖了被装饰函数的 __name__ 和 __doc__ 属性    def clocked(*args, **kwargs):        t0 = time.perf_counter()        result = func(*args, **kwargs)        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, result))        return result    return clocked复制代码二、使用functools.lru_cache做备忘
functools.lru_cache 是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。



# 用上一个装饰器clock@clockdef fib(n):    if n < 2:        return 1    return fib(n-2) + fib(n-1)if __name__ == '__main__':    fib(5)# 执行结果[0.00000000s] fib(1) -> 1 [0.00000000s] fib(0) -> 1 [0.00000000s] fib(1) -> 1 [0.00000000s] fib(2) -> 2 [0.00000000s] fib(3) -> 3 [0.00000000s] fib(0) -> 1 [0.00000000s] fib(1) -> 1 [0.00000000s] fib(2) -> 2 [0.00000000s] fib(1) -> 1 [0.00000000s] fib(0) -> 1 [0.00000000s] fib(1) -> 1 [0.00000000s] fib(2) -> 2 [0.00000000s] fib(3) -> 3 [0.00000000s] fib(4) -> 5 [0.00000000s] fib(5) -> 8 Process finished with exit code 0复制代码



# 用functools.lru_cache进行装饰@functools.lru_cache()@clockdef fib(n):    if n < 2:        return 1    return fib(n-2) + fib(n-1)if __name__ == '__main__':    fib(5)# 执行结果[0.00000000s] fib(1) -> 1 [0.00000000s] fib(0) -> 1 [0.00000000s] fib(2) -> 2 [0.00100088s] fib(3) -> 3 [0.00000000s] fib(4) -> 5 [0.00100088s] fib(5) -> 8 复制代码
三、参数选择functools.lru_cache(maxsize=128, typed=False)

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

四、单分派泛函数singledispatch
可以把整体方案拆成多个模块,甚至可以为你无法修改的类提供专门的函数,使用@singledispatch装饰的函数会变成泛函数
  • 1、singledispatch:标记处理object类型的基函数
  • 2、各个专门函数使用@<<base_function>>.register(<<type>>)装饰
  • 3、专门函数的名称无关紧要,_是个不错的选择,简单明了
  • 4、为每个需要处理的类型注册一个函数
  • 5、可以叠放多个register装饰器,让同一个函数支持不同类型



from functools import singledispatch@singledispatchdef show(obj):    print(obj, type(obj), "obj")# 参数字符串@show.register(str)def _(text):    print(text, type(text), "str")# 参数int@show.register(int)def _(n):    print(n, type(n), "int")# 参数元祖或者字典均可@show.register(tuple)@show.register(dict)def _(tup_dic):    print(tup_dic, type(tup_dic), "int")if __name__ == '__main__':    show(1)    show("xx")    show([1])    show((1, 2, 3))    show({"a": "b"})# 执行结果1 <class 'int'> intxx <class 'str'> str[1] <class 'list'> obj(1, 2, 3) <class 'tuple'> int{'a': 'b'} <class 'dict'> int复制代码



【转载】
作者:艾利金德
链接:https://juejin.im/post/5a4f793c51882573473d926b



3 个回复

倒序浏览
~(。≧3≦)ノ⌒☆
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马