p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
装饰器基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
假如有个名为 decorate 的装饰器:
@decorate def target(): pprint(‘running target()‘)
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
上述代码的效果与下述写法一样:
def target(): print(‘running target()‘) target = decorate(target)
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
两种写法的最终结果一样:上述两个代码片段执行完毕后得到的target 不一定是原来那个 target 函数,而是 decorate(target) 返回的函数
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
举个?? 装饰器通常把函数替换成另一个函数
1 def deco(func): 2 def inner(): 3 print(‘running in inner()‘) 4 return inner 5 6 @deco 7 def target(): 8 print(‘running in target()‘) 9 10 target()
以上代码执行的结果为:
running in inner()
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
严格来说,装饰器只是语法糖。如前所示,装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。有时,这样做更方便,尤其是做元编程(在运行时改变程序的行为)时。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
综上,装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二个特性是,装饰器在加载模块时立即执行。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
Python何时执行装饰器
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时),如 ?? 的registration.py 模块所示。
1 registry = [] 2 3 def register(func): 4 ‘‘‘ 5 :param func:被装饰器的函数 6 :return: 返回被装饰的函数func 7 ‘‘‘ 8 print(‘running register(%s)‘ % func) #获取形参func的的引用 9 registry.append(func) #获取的引用地址放入到类表中 10 return func #执行装饰器的时候返回func 11 12 @register 13 def f1(): 14 print(‘running f1()‘) 15 16 @register 17 def f2(): 18 print(‘running f2()‘) 19 20 def f3(): 21 print(‘running f3()‘) 22 23 def main(): 24 print(‘running main()‘) 25 print(‘registry ->‘, registry) 26 f1() 27 f2() 28 f3() 29 30 if __name__ == "__main__": 31 main()
以上代码执行的结果为:
running register(<function f1 at 0x101c7bf28>) running register(<function f2 at 0x101c83048>) running main() registry -> [<function f1 at 0x101c7bf28>, <function f2 at 0x101c83048>] running f1() running f2() running f3()
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
注意,register 在模块中其他函数之前运行(两次)。调用register 时,传给它的参数是被装饰的函数,例如 <function f1 at 0x101c7bf28>。
加载模块后,registry 中有两个被装饰函数的引用:f1 和 f2。这两个函数,以及 f3,只在 main 明确调用它们时才执行。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
使用装饰器改进“策略”模式
使用注册装饰器可以改进的电商促销折扣 ?? 回顾一下,示例 6-6 的主要问题是,定义体中有函数的名称,但是best_promo 用来判断哪个折扣幅度最大的 promos 列表中也有函数名称。这种重复是个问题,因为新增策略函数后可能会忘记把它添加到promos 列表中,导致 best_promo 忽略新策略,而且不报错,为系统引入了不易察觉的缺陷。下面的 ?? 使用注册装饰器解决了这个问题。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
?? promos 列表中的值使用 promotion 装饰器填充
1 promos = [] 2 3 def promotion(promo_func): 4 promos.append(promo_func) 5 return promo_func 6 7 @promotion 8 def fidelity(order): 9 """为积分为1000或以上的顾客提供5%折扣""" 10 return order.total() * .05 if order.customer.fidelity >= 1000 else 0 11 12 @promotion 13 def bulk_item(order): 14 """单个商品为20个或以上时提供10%折扣""" 15 discount = 0 16 for item in order.cart: 17 if item.quantity >= 20: 18 discount += item.total() * .1 19 return discount 20 21 @promotion 22 def large_order(order): 23 """订单中的不同商品达到10个或以上时提供7%折扣""" 24 distinct_items = {item.product for item in order.cart} 25 if len(distinct_items) >= 10: 26 return order.total() * .07 27 return 0 28 29 def best_promo(order): 30 """选择可用的最佳折扣""" 31 return max(promo(order) for promo in promos)
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
与函数策略给出的方案相比,这个方案有几个优点:
- @promotion装饰器突出了被装饰的函数的作用,还便于临时禁用某个促销策略,只需要把装饰器注释掉
- 促销折扣策略可以在其他的模块中定义,在系统中的任何地方都行,只要使用@promotion装即可
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica }
变量作用域规则
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }