您的位置:首页 > 编程语言 > Python开发

Python学习笔记——装饰器

2017-07-24 20:28 597 查看
今天学习了装饰器,把自己的理解,写在下面,方便大家参考,也方便我以后随时查阅。

注意:以下完全是自己学习后的理解,可能因为知识的局限性稍有错误。以后随着知识的扩充,会回来进行修正。

1. 装饰器介绍

装饰器,顾名思义,是用来装饰的,装饰对象是函数。个人理解用装饰器装饰某函数,就是对这个函数进行封装,封装后生成的新函数的函数名与原函数名相同(利用函数名只能调用到新函数,调用不到原函数)。

2. 装饰器原理

(想要学习装饰器,先要了解什么是闭包,想要学习的请参考我以前写的 Python学习笔记——闭包

2.1 理论基础

这里简单说下个人理解

装饰器的实现基础,是闭包(可以理解成装饰器其实是闭包的一种应用/实现形式)

闭包的实现基础是函数体地址化,即

def t():
print('t')
t1 = t


t() 与 t1() 拥有同等效力,就是调用上述函数,这样以来,无论是t还是t1都可以当做参数被传送到任意位置,在任意位置调用t()或者t1()就会调用上述函数。

上面几句话是理论基础。

2.2 原理解析

下面我们以 代码 + 图 的形式来阐释并加深理解:



在图中,代码中红线以上部分,如图形中红线所画。绿线部分是装饰器w对t的装饰过程。

红线部分:py君遇到了方法w,直接开辟新空间,将空间地址存放在w中,然后跳过w函数。

又遇到了t方法,同样重复上面工作,开辟新空间,将空间地址放在t中,然后跳过t函数。

只执行t函数输出打印t

绿线部分:py君看到了w(t),实参为t,形参为func,执行w函数,发现里面还有一个w_in函数,开辟新空间,将空间地址放在w_in中,然后跳过w_in函数,执行return w_in,t = w(t),w_in中存放的地址复制到了t中,t原来地址被覆盖。

到此装饰器w对t函数装饰完毕,形成一个函数名为t的新函数。

然后我们调用函数t,相当于调用w_in函数,也就是w函数的闭包。

w_in函数先输出了w,然后调用了func函数(就是原来的t函数),输出了t。

程序结束。

2.3 分析&总结

由上面的代码结果我们可以看出,同样是调用t函数,在装饰前和装饰后,输出的结果就不一样。

装饰器的好处是在不改变函数原有代码逻辑与其调用方式的情况下,对其功能进行扩充。

举个例子说明好处,A写了一个函数a,B对函数a进行了调用,运行没有出什么问题,这时候因为需求的改变,要对B进行权限的认证,认证通过了,B才能调用函数a,这时候可以用几种方法解决:

(1) B修改对应代码,添加权限验证功能
(2) A修改函数a,函数开始的地方添加权限验证。
(3) A编写新函数b,对函数a进行封装,在函数b中先进行权限验证,验证通过后调用函数a
(3) A利用装饰器对函数a进行装饰


解决方法1:若CDEFGI…..都调用了函数a,那都得自己添加权限验证功能,太繁琐。

解决方法2:比解决办法一好,但是修改原函数,风险太大,本来没有任何bug,可能修改后就出问题了。带来不必要的麻烦。

解决方法3:封装后,其他调用者都需要将调用方法改变,同样过于繁琐。

解决方法4:沿着解决办法3的思路,利用装饰器对函数a进行高效封装,且不改变原有调用方法,完美解决了问题。

3. 使用装饰器

3.1 单装饰器

原理解析中,其实是为了方便大家理解,才这样写的,真正在使用装饰器的时候要比上面的代码简单许多。



右边的代码是我们刚才的在阐释原理的时候用的代码,左边的代码才是实际使用的形式。

两个被红框圈住的代码区域其实是等效的。

在这里我们把左边红框圈住的单纯的认为是一个新函数。

是不是很简单。

3.2 多装饰器



多装饰器的情况,其实也很简单,只要学会分析就好。

@jiacu
@xieti
@ccolor
def tprint()
print('hello world')
return 'hello world'


之前说过,我们要把装饰器和被装饰函数看做一个新函数(被装饰函数其实就是装饰器函数的参数),这样多装饰器就好理解了。

@jiacu修饰的函数是

@xieti
@ccolor
def tprint()
print('hello world')
return 'hello world'


@xieti修饰的函数是

@ccolor
def tprint()
print('hello world')
return 'hello world'


@ccolor修饰的函数是

def tprint()
print('hello world')
return 'hello world'


外层装饰器要装饰,就需要先弄清被装饰函数,也就是说,以上对tprint函数装饰顺序应为 @ccolor @xieti @jiacu,装饰完毕后。@jiacu最后进行装饰,所以tprint中的地址指向的是jiacu的闭包,从加粗的闭包开始执行。

也就是说,在多装饰器情况下,装饰的时候是从后往前装饰,执行的时候是从前往后执行。

以下内容更新于2017年7月28日

3.3 装饰有参数的函数

如果被装饰函数有参数呢?

我们来看图:



printab(’s’,’y’) ——> 其实调用的是fun_in(a,b)方法

先打印fun_in

再调用funcName(a,b) ——>调用的是原来的printab(a,b)<
4000
/p>

也就是将’s’,’y’当做参数传递给fun_in的形参a,b后来又作为实参传给了funcName方法内,

最后执行了 原来printab的方法 print(‘a=%c,b=%c’%(a,b))(此时ab就是’s’,’y’)

被修饰函数有参数,就需要闭包有相应的参数。

以下内容更新于2017年7月29日

3.4 装饰不定长参数的函数

有写函数会需要多个参数,这时候我们要修饰它的话,应该怎么做呢?

我们原来学过不定长参数的表达方式,下面先复习一下

利用 *args 以元祖的形式接收若干个参数,

利用 **kwargs 以字典的形式接收若干个参数,

例子如下:



顺手把迭代对象和迭代器也复习了下。

那我们应该如何使用装饰器装饰这个函数呢?

刚才说了被装饰的函数有多少个参数,闭包就得写多少个参数而且类型都得一样,闭包对原方法的调用,参数个数和类型也需要和被装饰的函数一致,那我们就试着写一下吧。



是不是成功了?其实就是这么简单。

被修饰函数,闭包,闭包内对原函数的调用,这三个的参数的个数和类型以及顺序都是一模一样的。记住这一点,装饰不定长参数函数就不是问题。

3.5 装饰带返回值的函数

下面我们看一个带返回值的函数



加装饰器



在闭包中,原来是直接调用func方法就可以了,现在换成了return func(),

我们来看一张图



在装饰完成之后

ret = returnsy()

调用returnsy方法就相当于调用的是zhuangshiqi_in方法

执行方法先打印zhuangshiqi_in

执行return func()

这句话的意思就是把func方法的结果返回

那我们看func方法,方法直接返回了字符串’sy’

所以return func()就相当于return ‘sy’了

所以ret = ‘sy’

打印出了 sy

3.6 通用装饰器

上面说了这么多中装饰器,针对一种情况固然好,必经程序猿,懒是天性,有些事儿能省就省,所以,我们要寻求一种能应对所有函数的装饰器——通用装饰器。

我们都知道,如果一个函数有返回值,但是我们不接收,那么这个函数值返回的结果我们只能进行一次性使用;相反如果一个函数没有返回值,但是我们用变量进行接收了,那么这个接收的变量就等于null或者none。

如下:



同样,我们使用不定长参数函数也是可以选择写参数或者不写参数的。

如下:



(这里注意一点,如果字典类型参数只能出现在普通参数的后面,否则会报错

SyntaxError: positional argument follows keyword argument)

糅合 装饰不定长参数函数,装饰有返回值参数,再了解了上面的这个例子,我们就可以写出通用装饰器了。

# 通用装饰器定义
def tongyong(func):
# 闭包 注意带不定长参数
def tongyong_in(*args,**kwargs):
print('tongyong_in')
# 注意这里func有不定长参数
return func(*args,**kwargs)
return tongyong_in

# 有返回值有不定长参数函数
@tongyong
def demo(*args,**kwargs):
print('有返回值有不定长参数函数')
for x in args:
print(x)
for key in kwargs:
print(key,':',kwargs[key])
return 'sy'

# 有返回值单参数函数
@tongyong
def demo0(s):
print('有返回值单参数函数')
print(s)
return 'sy'

# 无返回值无参数函数
@tongyong
def demo1():
print('无返回值无参数函数')
print('sy')

s = demo(10,20,s=10,y=20)

print('有返回值打印:',s)

y = demo0({'a':10,'b':20})

print('有返回值打印:',s)

z = demo1()

print('无返回值打印:',z)


上面是我现写的demo,不是很好,大家理解了,自己写一个通用装饰器然后,用它来装饰任意函数试一试吧。(滑稽)

在我理解,通用装饰器是大多时候通用的,并不是万能的,在有些情况是需要进行小修改才可以使用的。所以说我们应该理解装饰器的运行机制然后根据我们的需求来编写装饰器。

3.6 带参数的装饰器

如果上面说的内容你都理解了,那么带参数的装饰器就再简单不过了

带参数装饰器,其实是闭包中套闭包。

def zhuangshiqi_out(s):
# 闭包1 装饰器
def zhuangshiqi(func):
# 闭包2
def zhuangshiqi_in():
print('zhuangshiqi_in')#装饰器内部逻辑
print(s)#装饰器内部逻辑
func()
return zhuangshiqi_in
return zhuangshiqi

@zhuangshiqi_out('sy')
def demo():
print('我是被装饰函数')

demo()


闭包1才是真正的装饰器,带参数的装饰器其实就是为了传一个参数为装饰器内部逻辑所用。

我的理解是,在上面代码中标有装饰器内部逻辑的两行是在调用原函数之前执行的,如果没有这两行,那么有没有装饰器都是一样的,装饰器的实质就是为了在原函数执行之前能够执行一些逻辑,而这些逻辑有可能是需要一些参数的,这就是带参数装饰器存在的意义。

4 总结

以上就是学习装饰器所有的内容。

笔记是为了以后我方便回忆相关知识用的,写的比较垃圾,但是好在我自己能够理解,大家如果有看不懂的地方可以留言,我会回复的。

再强调一下,装饰器其实是一种闭包的应用,是一种编程思想的提现,我们一定要理解其工作原理,切忌死记硬背。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python