Python 函数装饰器和闭包

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 函数装饰器和闭包相关的知识,希望对你有一定的参考价值。

装饰器基础知识

  装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

假如有个名为 decorate 的装饰器:

@decorate
def target():
    pprint(running target())

上述代码的效果与下述写法一样:

def target():
    print(running target())
    
target = decorate(target)

两种写法的最终结果一样:上述两个代码片段执行完毕后得到的target 不一定是原来那个 target 函数,而是 decorate(target) 返回的函数

举个?? 装饰器通常把函数替换成另一个函数

 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()

  严格来说,装饰器只是语法糖。如前所示,装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。有时,这样做更方便,尤其是做元编程(在运行时改变程序的行为)时。

  综上,装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二个特性是,装饰器在加载模块时立即执行

 

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()

注意,register 在模块中其他函数之前运行(两次)。调用register 时,传给它的参数是被装饰的函数,例如 <function f1 at 0x101c7bf28>。

加载模块后,registry 中有两个被装饰函数的引用:f1 和 f2。这两个函数,以及 f3,只在 main 明确调用它们时才执行。

 

使用装饰器改进“策略”模式

   使用注册装饰器可以改进的电商促销折扣 ?? 回顾一下,示例 6-6 的主要问题是,定义体中有函数的名称,但是best_promo 用来判断哪个折扣幅度最大的 promos 列表中也有函数名称。这种重复是个问题,因为新增策略函数后可能会忘记把它添加到promos 列表中,导致 best_promo 忽略新策略,而且不报错,为系统引入了不易察觉的缺陷。下面的 ?? 使用注册装饰器解决了这个问题。

?? 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)

与函数策略给出的方案相比,这个方案有几个优点:

  1. @promotion装饰器突出了被装饰的函数的作用,还便于临时禁用某个促销策略,只需要把装饰器注释掉

  2. 促销折扣策略可以在其他的模块中定义,在系统中的任何地方都行,只要使用@promotion装即可

 

变量作用域规则

 

以上是关于Python 函数装饰器和闭包的主要内容,如果未能解决你的问题,请参考以下文章

Python装饰器和闭包函数

Python 函数装饰器和闭包

python基础-装饰器和偏函数

流畅的python第七章函数装饰器和闭包学习记录

python的装饰器和闭包

python的装饰器和闭包