什么是python装饰器(Decorators)?轻松理解Decorators

装饰器(Decorators)是Python学习中迈不过的坎,也是编写出好的Python代码的一大利器,今天我们就由浅入深的学习这一技能包!

在学习装饰器之前,我们先对它的作用有一个大致的了解:装饰器用来以某种方式增强函数的行为。

不理解没关系,我们先来学习几个装饰器的例子。

基础学习:

一、装饰器初体验:

一个简单的装饰器: 

  eg1:
1
def decorate(func): 2 def new_func(): 3 print("running new_func()") 4 return new_func 5 6 @decorate 7 def prim_func(): # 使用decorate装饰prim_func, 8 print("running prim_func()") 9 10 prim_func() 11 12 #output:running new_func() # 函数prim_func的功能变为new_func

解释:上例中decorate即为装饰器,其语法为:@+装饰器函数名。例子中prim_func()函数的定义其实相当于做了两件事:

1 def prim_func():
2     print("running prim_func()") # 定义prim_func函数
3 
4 prim_func = decorate(prim_func) # 装饰prim_func函数

上述装饰器将被装饰函数替换成另一个函数。实际上装饰器的两个主要的作用就是:

  ①、将被装饰函数替换成另一个函数或可调用对象(对应上例)。

  ②、处理被装饰的函数,然后将他返回。这种用法见下例:

  eg2:
1
def decorate(func): 2 print("running decorate(%s)"%func) 3 return func 4 5 @decorate 6 def prim_func(time): # 装饰 7 print("running prim_func()","第%d次"%time) 8 9 prim_func(1) #第一次调用 10 prim_func(2) #第二次调用 11 12 # output: 13 # running decorate(<function prim_func at 0x008AA270>) 14 # running prim_func() 第1次 15 # running prim_func() 第2次

两个例子一对比,不难产生如下疑问:

  • 同样是装饰器,为什么eg1采用了函数嵌套,eg2不使用函数嵌套?

    答:eg1中创建了新的函数new_func,增强了原函数prim_func函数的功能,用以对prim_func做修改;而eg2中没有对原函数prim_func做修改,故不需要创建新的函数也就不需要嵌套另外一个函数。

  • 为什么eg2中函数第一次调用有输出“running decorate(<function prim_func at 0x008AA270>)”,第二次调用没有?

    答:这其实涉及到何时执行装饰器的问题,例子中的输出“running decorate(<function prim_func at 0x008AA270>)”并不是由于函数prim_func的调用,而是由于装饰器的执行。函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。所以例子中装饰器的运行是在所有函数调用之前,和prim_runc的调用无关。

二、叠放装饰器:

  有时候我们会看到一个函数被两个甚至多个装饰器装饰,其实也很好理解,例如:

1 @d1
2 @d2
3 def f():
4     print('running f()')

  等同于:

1 def f():
2     print('running f()')
3 
4 f = d1(d2(f))

  所以,不要被它的外表给吓到了,多个装饰器的叠放可以此类推……

典型应用:

一、注册装饰器改进“策略”模式

  问题背景:《设计模式:可复用面向对象软件的基础》一书是这样描述“策略”模式的:定义一系列算法,把他们一一封装起来,并且使他们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。(不理解没关系,我们看下面一个例子:)

  具体问题:假设电商领域某个网店制定了三种折扣规则,顾客每次只能享受一个折扣,如何设计程序选择最佳折扣?

普通函数式解法:

 1 promos = [promotion_1, promotion_2, promotion_3]
 2 
 3 def promotion_1(order): # 第一种折扣
 4     pass
 5 
 6 def promotion_2(order): # 第二种折扣
 7     pass
 8 
 9 def promotion_3(order): # 第三种折扣
10     pass
11 
12 def best_promo(order): # 选择可用的最佳折扣
13     return max(promot(order) for promo in promos)

  上例中我们为三种不同折扣分别创建一个函数(promotion_x)计算优惠量,并创建一个函数列表(promos)保存不同的折扣函数,最后创建一个函数(best_promo)用来选择函数列表中的最佳折扣。

  上例易于阅读,而且可以达到设计目的,但也有些不易察觉的缺陷:若想添加新的促销策略,要定义相应的函数,还要记得把它添加到promos列表中;否则,当添加新促销后,best_promo不会考虑到它。通过注册装饰器可以很轻松地解决这个问题,请看例子:

 1 promos = []  # 列表起初是空的
 2 
 3 def promotion(promo_func): # 装饰器在模块导入时就将所有折扣策略添加到列表promos中
 4     promos.append(promo_func)
 5     return promo_func # 将原函数原封不动地返回
 6 
 7 @promotion
 8 def promotion_1(order): # 第一种折扣
 9     pass
10 
11 @promotion
12 def promotion_2(order): # 第二种折扣
13     pass
14 
15 @promotion
16 def promotion_3(order): # 第三种折扣
17     pass
18 
19 def best_promo(order): # 选择可用的最佳折扣
20     return max(promot(order) for promo in promos)

  这种方法不仅完美地解决了上述问题,还有如下优点:

  • @promotion装饰器突出了被装饰的函数的作用,还便于临时禁用某个促销策略:只需要把装饰器注释掉。
  • 促销折扣策略可以在其他模块中定义,在系统的任何地方都行,只要使用@promotion装饰即可。

二、另一个例子:

  下例实现一个简单的装饰器,在每次调用函数的装饰器时计时,然后把经过的时间、传入的参数和调用的结果打印出来。

 1 import time
 2 
 3 def clock(func):
 4     def clocked(*args):
 5         t0 = time.perf_counter()
 6         result = func(*args)  # clocked的闭包中包含自由变量func
 7         tim = time.perf_counter() - t0
 8         name = func.__name__
 9         arg_str = ', '.join(repr(arg) for arg in args)
10         print('[%0.8fs] %s(%s) -> %r ' % (tim, name ,arg_str, result))
11         return result
12     return clocked  # 返回内部函数,取代被装饰的函数
13 
14 @clock
15 def add(data1, data2, data3):
16     return data1 + data2 + data3
17 
18 if __name__ == '__main__':
19     a = add(1, 3, 5)
20     print(a)
21
22 # output:
23 # [0.00000100s] add(1, 3, 5) -> 9
24 # 9

  本例中我们实现了一个简单的装饰器,增强了原函数的行为。从上例中,可以学到:①、原函数为带参函数时,我们可以通过可变参数实现装饰器。②、原函数有返回值时,在创建新函数中执行原函数,并将返回值返回。

  下面是一个装饰器的模板,用来装饰带有参数,并且有返回值的函数。

 1 def decorator(func):
 2     def inner(*args, **kwargs): # 可变参数
 3         print('add inner called')
 4         result = func(*args, **kwargs)  # 执行原函数
 5         return result 6     return inner
 7 
 8 @decorator
 9 def add(a, b):
10     return a + b
11 
12 @decorator
13 def add2(a, b, c):
14     return a + b + c
15 
16 print(add(2, 4))
17 print(add2(2, 4, 6))

现在,我们基本了解了装饰器,也能够自己实现一些装饰器来改善自己的代码。那我们就完全掌握装饰器了吗?还没有,还需要我们不断深入。

装饰器进阶:

一、参数化装饰器(带参数的装饰器):

  通过之前的函数我们了解到,装饰器的参数通常为被装饰的函数,那装饰器还能接受其他参数吗?怎么让装饰器接收其他参数呢?答案是:使用带参数的装饰器:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。不明白?请看例子:

 1 import time
 2 
 3 DEFAULT_FMT = '[%0.8fs] %s(%s) -> %r '
 4 
 5 def clock(fmt=DEFAULT_FMT):  # 装饰器工厂函数
 6     def decorate(func):  # 装饰器
 7         def clocked(*args):
 8             t0 = time.perf_counter()
 9             result = func(*args)  
10             tim = time.perf_counter() - t0
11             name = func.__name__
12             arg_str = ', '.join(repr(arg) for arg in args)
13             print(fmt % (tim, name ,arg_str, result))
14             return result
15         return clocked  # 返回内部函数,取代被装饰的函数
16     return decorate
17 
18 @clock() # clock是装饰器工厂函数,必须作为函数调用
19 def add(data1, data2, data3):
20     return data1 + data2 + data3
21 
22 @clock('test:[%0.8fs] %s(%s) -> %r ') # 其实相当于add2 = clock('test:[%0.8fs] %s(%s) -> %r ')(add2)
23 def add2(data1, data2, data3):
24     return data1 + data2 + data3
25 
26 if __name__ == '__main__':
27    a = add(1, 3, 5)
28    print(a)
29 
30    b = add2(1, 3, 5)
31    print(b)
32 
33 # output:
34 # [0.00000090s] add(1, 3, 5) -> 9 
35 # 9
36 # test:[0.00000040s] add2(1, 3, 5) -> 9 
37 # 9

  本例只是对前面的例子做了一个简单的改造,可以看出,带参数的装饰器其实就是一个装饰器工厂函数,通过为装饰器工厂函数赋不同的值,可以得到不同的装饰器。

二、类装饰器:

< 待添加……>

猜你喜欢

转载自www.cnblogs.com/jiandanyidian/p/12271568.html