一、说明
装饰器,顾名思义就是对某个东西进行化妆,让它更好看一些,在Python中装饰器的作用就是对函数进行装饰。那么问题来了,怎么装饰呢?这里就用到了前面介绍函数时,说过的几个点:
- 函数名可以赋值给其他变量
- 函数名可以作为参数
- 函数名可以当做容器类的元素
- 函数名可以作为函数的返回值。
- 闭包
以上几点,被使用在装饰器中,所以一定要理解这几点才可以看下面的内容。
二、讲个故事
我写了一个软件,对这个操作过程进行一下简单的描述:
def openSoft(): print('打开手机') print('找到软件并点击,就打开了') openSoft()
是不是so easy。好了,大多数软件都会有登录功能。现在,我也要加上,但是我的function(),我不想修改,怎么办呢?直接在“打开手机”前,再写一个“登录”就好了,但是有没有想过,如果只有一处调用,还好改,但是如果有很多处调用了这个openSoft()呢?而且,有的地方,我不需要登录功能,还保持原来的,这样就尴尬了,只能重新写一个函数了。这时就体现出装饰器的作用了。看代码:
def openSoft(): print('打开手机') print('找到软件并点击,就打开了') def wrapper(f): def inner(): print('输入手机密码') f()#在这里,调用openSoft() return inner#把inner作为返回值,返给调用的地方 ret = wrapper(openSoft)#获取到inner,ret接收到的是inner的内存地址,使用id查看 ret()#加上()后,这里就是调用inner了
可以看到的,我没有对openSoft动手,只是又写了一个嵌套的函数。而且也很完美的把,登录功能,添加上了。接下来,又有一个问题了,手机有密码,怎么办呢?最开始使用openSoft()的地方不想改,怎么操作?看代码:
def wrapper(f): def inner(): print('输入手机密码') f()#在这里,调用openSoft() return inner#把inner作为返回值,返回去 def openSoft(): print('打开手机') print('找到软件并点击,就打开了') openSoft = wrapper(openSoft)#获取到inner openSoft()#加上()后,这里就是调用inner了
看到问题所在了吗?我把ret改成了openSoft,这样openSoft就被重新赋值了,也就代表了新的函数。运行可以看到,与上面ret时,是一样的。代码添加了,不少,本着,越少代码越好的原则,再看一下下面的代码:
def wrapper(f): def inner(): print('输入手机密码') f()#在这里,调用openSoft() return inner#把inner作为返回值,返回去 @wrapper#这里相当于:openSoft = wrapper(openSoft)#获取到inner def openSoft(): print('打开手机') print('找到软件并点击,就打开了') openSoft()
运行的结果与上面还是一样的,可以看到代码少了一些,看着也简单,但是也出现一个新的东西----“@”。这个@,在这里叫做语法糖,就是给装饰器使用的。
完美对的解决了一些问题,接下来,再思考一个问题,软件打开了,我得告诉用户一下,也就是给个返回值。我想从openSoft中返回,告诉用户“恭喜你,软件打开了!”。再看代码:
def wrapper(f): def inner(): print('输入手机密码') ret = f()#在这里,调用openSoft(),并接收到返回值 return ret#把openSoft里面的值,在这里继续返回给调用者 return inner#把inner作为返回值,返回去 @wrapper def openSoft(): print('打开手机') print('找到软件并点击,就打开了') return '恭喜你,软件打开了!' ret = openSoft() print(ret)
Very nice,接收到了。这里想说明一点的是,openSoft返回值,后还得使用inner,再向上一层调用者继续返回一下。也就是,那里调用的,得返给哪里。
现在又有一个问题了,我要知道什么牌子打开了手机而且openSoft中显示“打开了xxx手机”,这个“xxx”是个动态接收的地方。再看代码,在装饰器中怎么传递参数。
def wrapper(f): def inner(phone_name): print('输入手机密码') ret = f(phone_name)#在这里,调用openSoft() return ret return inner#把inner作为返回值,返回去 @wrapper def openSoft(name): print(f'打开{name}手机') print('找到软件并点击,就打开了') return '恭喜你,软件打开了!' ret = openSoft("华为") print(ret)
结果又是完美的,“打开了华为手机”。这里对openSoft进行了修改,目的是为了表示出传参的操作。
总结一下,看看装饰器的结构是什么样的?
# 装饰器: 对传递进来的函数进⾏包装. 可以在⽬标函数之前和之后添加任意的功能. def wrapper(func): def inner(*args, **kwargs): '''在执⾏⽬标函数之前要执⾏的内容''' ret = func(*args, **kwargs) '''在执⾏⽬标函数之后要执⾏的内容''' return ret return inner # @wrapper 相当于 target_func = wrapper(target_func) 语法糖 @wrapper def target_func(): print("我是⽬标函数")
这是一个超级无敌的模式,好好记住了吧。
上面的公式记好了,下面再来看一个问题,我的手机如果没有黑屏,一直在玩呢,要想打开这个软件就不需要再次输入密码了。所以,我需要一个开关来控制一下,是否需要输入密码。看代码:
isN = True#控制全局的变量,在这里可以遥控手机是否需要输入密码 def wrapper_outer(isNeed):#在原来的基础上又加了一层 def wrapper(f): def inner(phone_name): if isNeed: print('输入手机密码') ret = f(phone_name) # 在这里,调用openSoft() return ret else: ret = f(phone_name) # 在这里,调用openSoft() return ret return inner#把inner作为返回值,返回去 return wrapper @wrapper_outer(isN)#在这里输入,True或者False。wrapper_outer(isN)获得wrapper def openSoft(name): print(f'打开{name}手机') print('找到软件并点击,就打开了') return '恭喜你,软件打开了!' ret = openSoft("华为") print(ret)
咱们之前的写法是@wrapper 其中wrapper是⼀个函数. 那么也就是说. 如果我能让wrapper这⾥换成个函数就⾏了. wrapper(True)返回的结果是wrapper也是⼀个函数啊. 刚刚好和前⾯的@组合成⼀个@wrapper. 依然还是原来那个装饰器. 只不过这⾥套了3层. 但你要能看懂. 其实还是原来那个装饰器@wrapper
上面是套在一起进行装饰,再看下面的摞在一起的。
def wrapper2(fn): def inner(*args, **kwargs): print("333") ret = fn(*args, **kwargs) print("444") return ret return inner def wrapper1(fn): def inner(*args, **kwargs): print("111") ret = fn(*args, **kwargs) print("222") return ret return inner @wrapper2 @wrapper1 def eat(): print("我想吃⽔果") eat() 结果: 333 111 我想吃⽔果 222 444
执⾏顺序: ⾸先@wrapper1装饰起来. 然后获取到⼀个新函数是wrapper1中的inner. 然后执⾏@wrapper2.这个时候. wrapper2装饰的就是wrapper1中的inner了. 所以. 执⾏顺序就像:第⼆层装饰器前(第⼀层装饰器前(⽬标)第⼀层装饰器后)第⼆层装饰器后. 程序从左到右执⾏起来. 就是我们看到的结果
Python三大器:迭代器,生成器,装饰器。值得好好学习一下。