
不碰原函数一行代码给它加上计时、日志、权限……46.1 从包装礼物说起装饰器诞生import time # 装饰器: 一个加工函数的函数 —— 闭包的直球应用 def timer(func): # 接收一个函数 def wrapper(*args, **kwargs): # 造一个加强版替身 start time.time() result func(*args, **kwargs) # 替身里调用真身 end time.time() print(f{func.__name__} 耗时 {end - start:.4f} 秒) return result # 真身的返回值原样转交 return wrapper # 把替身退货 def slow_add(a, b): time.sleep(0.3) # 假装在辛苦计算 return a b # 手动包装版理解用: new_add timer(slow_add) # 送去加工, 拿回替身 print(new_add(1, 2)) # 耗时 0.3xxx 秒 3 # 语法糖版日常写法: 完全等价于上面两行! timer def slow_mul(a, b): time.sleep(0.2) return a * b print(slow_mul(3, 4)) # 耗时 0.2xxx 秒 12装饰器, 等同于手机壳工厂。你那个手机当作原函数送去工厂, 工厂不会去拆开手机也不会对手机内部进行改装, 也就是原代码一行都不会变动, 只是会给手机套个壳当作替身: 壳上面还带着充电宝具备计时功能、带着挂绳具备日志功能。拿回来的那个有着新外观和新功能的手机, 调用这种调用方式、各类参数、返回值全部都保持兼容, 然而一使用每次都会多出壳所具备的能力。timer 这一行的意义, 就是把下面的那个函数派送到壳工厂制作, 这全然等同于 timer()。46.2 通用模板逐行精讲通用模板def my_decorator(func): def wrapper(*args, **kwargs): # ---- 前置增强: 调用真身之前做的事 ---- print(函数要开始了) # ---- 调用真身, 参数原封不动转发 ---- result func(*args, **kwargs) # ---- 后置增强: 调用真身之后做的事 ---- print(函数结束了) # ---- 返回值原样交还调用方 ---- return result return wrapper # 模板四要素(缺一个都是残次装饰器): # 1. wrapper(*args, **kwargs) ← 万能参数袋, 接住任何调用方式 # 2. func(*args, **kwargs) ← 原样转发, 别私自加参数 # 3. result ... ; return result ← 返回值转发, 别吞! # 4. return wrapper ← 交还替身 # # 忘记 return result 是新手第一大 bug: # 被装饰的函数从此做什么都返回 None高频翻车的现场情况如下: 其一, 仅仅书写 (self) 或者 (a, b) , 一旦碰到具有别的签名的函数就会崩溃, 所以请务必始终使用 *args/** 这种万能袋形式其二, 忘记书写, 结果原函数好不容易计算出来的值被你给丢弃了, 导致调用方获取到 None其三, 在特定环境里直接 print(func(*args)) , 而不是先将其存储为变量之后再进行 print 操作, 如此一来返回值就会被 print 给消耗从而变成 None。请对照模板逐一项进行自我检查。辨析装饰完的实际情况: 到达timer时段之后, 涉及之名称, 已然不朝原函数归指了, 却是通向替体, 因此, 发起print(.)指令后, 将会给出——而非—— 名称、文档字符串俱被替体所替代。若期盼恢复原初“身份指代信物”, 敬请运用下一节内容当中的wraps(func) 一行装饰代码具备专治身份缺失的效能。当下, 请先将此现象予以记取等到下一节课再着手阐述其内在根基。自我测试: ① timer 等同于哪一行代码? ② 为何非得使用 *args/**? ③ 装饰品使得增添了计时方面的能力, 原本的函数代码改动了几行呢? 0 行——这便是“开放扩展、封闭修改”的函数式的实现方式④ 上一节课闭包的三个要素在本节课各自对应着什么? 嵌套 在 timer 当中引用外层 引用 func返回内层——装饰品的确就是闭包, 恭喜你已然学会了。️ 本课练习练习 1写装饰器 函数被调用时打印函数名被调用了调用后打印函数名执行完毕。套在一个 函数上验证。单纯地去套那个模板, func。着手取函数名, 前头放一个print后置也放一个print。留意中间务必是 func(*args, **) 而后 ——哪怕 确实 不存在也是需要转发过去即便情况是None同样得转。写下装饰器, 去统计函数被调用的次数, 运用计数这种方式, 函数每次被调用之后打印 “第 N 次调用” , 连续调用 3 次来验证 1/2/3。请你明确一下问题, 比如对这段内容进行润色表述、提取关键信息、根据其进行拓展等等, 这样我才能更有针对性地按照要求改写。仅从目前提供的内容看, 似乎并不是原需要按照格式改写的句子形式。你可以再清晰说明一下具体希望得到怎样的改写结果。练习 3: 去写一个名为 cache 的装饰器, 这个装饰器能够缓存函数的结果, 当遇到同一参数再次进行调用的时候, 会直接返回缓存的结果, 而不再进行计算, 并且要打印出命中缓存这句话。要用慢函数 fib 或者 sleep 来验证第二次调用的时候能够瞬间返回成功。外层进行建立一个空的集合, 里层首先使用参数当作键来进行查找, 需要注意的是可变参数是不能够当作键的, 仅测试的时候用单参数函数就行, 要是查到了的话就进行相应处理, 要是查不到的话才进行等于调用函数并将结果存进去。这便是所谓的“记忆化”, 而此乃是动态规划优化的最为核心的技巧。练习4: 思考题目: 为何会说, “装饰器达成了对于扩展是开放的、对于修改是关闭的”这种情况? 它跟直接于函数之内添加print计时代码相较, 产生的优势是啥?直接改动函数体就是侵入式修改: 每个函数都得进行计时代码的复制以及粘贴操作, 若想要去掉时还得再逐个去删除。装饰器属于非侵入式: 其功能写在装饰器内部某处, 只需通过的方式就能给函数加上功能, 去掉便会失去该项功能, 并且能够给任意的函数重复使用。这样实现了代码复用以及关注点分离, 有着一举两得的效果。