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

本文详细介绍了Python装饰器的概念和用法,从基础的装饰器定义到叠放装饰器的应用,再到参数化装饰器和类装饰器的实现。通过实例展示了如何使用装饰器改进策略模式、实现函数计时以及动态管理函数行为。同时,文章探讨了装饰器如何用于注册函数、增强函数功能以及如何处理带参数和返回值的函数。
摘要由CSDN通过智能技术生成

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

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

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

基础学习:

一、装饰器初体验:

一个简单的装饰器:

eg1:

1 defdecorate(func):2 defnew_func():3 print("running new_func()")4 returnnew_func5

6 @decorate7 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 defprim_func():2 print("running prim_func()") #定义prim_func函数

3

4 prim_func = decorate(prim_func) #装饰prim_func函数

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

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

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

eg2:

1 defdecorate(func):2 print("running decorate(%s)"%func)3 returnfunc4

5 @decorate6 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()

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()”,第二次调用没有?

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

二、叠放装饰器:

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

1 @d12 @d23 deff():4 print('running f()')

等同于:

1 deff():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 @promotion8 def promotion_1(order): #第一种折扣

9 pass

10

11 @promotion12 def promotion_2(order): #第二种折扣

13 pass

14

15 @promotion16 def promotion_3(order): #第三种折扣

17 pass

18

19 def best_promo(order): #选择可用的最佳折扣

20 return max(promot(order) for promo in promos)

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

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

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

二、另一个例子:

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

1 importtime2

3 defclock(func):4 def clocked(*args):5 t0 =time.perf_counter()6 result = func(*args) #clocked的闭包中包含自由变量func

7 tim = time.perf_counter() -t08 name = func.__name__

9 arg_str = ','.join(repr(arg) for arg inargs)10 print('[%0.8fs] %s(%s) -> %r' %(tim, name ,arg_str, result))11 returnresult12 return clocked #返回内部函数,取代被装饰的函数

13

14 @clock15 defadd(data1, data2, data3):16 return data1 + data2 +data317

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 defdecorator(func):2 def inner(*args, **kwargs): #可变参数

3 print('add inner called')4 result = func(*args, **kwargs) #执行原函数

5 return result6 returninner7

8 @decorator9 defadd(a, b):10 return a +b11

12 @decorator13 defadd2(a, b, c):14 return a + b +c15

16 print(add(2, 4))17 print(add2(2, 4, 6))

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

装饰器进阶:

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

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

1 importtime2

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() -t011 name = func.__name__

12 arg_str = ','.join(repr(arg) for arg inargs)13 print(fmt %(tim, name ,arg_str, result))14 returnresult15 return clocked #返回内部函数,取代被装饰的函数

16 returndecorate17

18 @clock() #clock是装饰器工厂函数,必须作为函数调用

19 defadd(data1, data2, data3):20 return data1 + data2 +data321

22 @clock('test:[%0.8fs] %s(%s) -> %r') #其实相当于add2 = clock('test:[%0.8fs] %s(%s) -> %r ')(add2)

23 defadd2(data1, data2, data3):24 return data1 + data2 +data325

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

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

二、类装饰器:

< 待添加……>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值