Python函数之五:装饰器
一、什么是装饰器
装饰器就是在不改变原被装饰的函数的源代码以及调用方式下,为其添加新的功能。
装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。
通过一个实例,来理解python的装饰器:
需求:给汽车的轮胎涂色
def car():
color = 'red'
print(f'汽车的颜色:{color}')
car()#汽车的颜色:red
1、直接在原代码上添加功能
def car():
color = 'red'
print(f'汽车的颜色:{color}')
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
car()
#汽车的颜色:red
#汽车的轮胎颜色:green
分析:直接在原代码上添加功能虽然也完成了需求,但是如果设想要给一个汽车厂一万辆汽车轮胎涂色,那么每一次涂色都要复制以上添加的代码,工作量非常之大。
2、通过函数封装功能
def car():
color = 'red'
print(f'汽车的颜色:{color}')
make_color()
def make_color():
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
car()
#汽车的颜色:red
#汽车的轮胎颜色:green
分析:通过函数封装的方法,确实使代码变得简单了一些,但是为一万辆汽车轮胎涂色,每次都要通过添加代码去调用 make_color()函数,工作量还是非常大的。
3、通过动态传参的方法
def car():
color = 'red'
print(f'汽车的颜色:{color}')
def make_color(f):
f()
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
make_color(car)
分析:在没有改变原本car()的代码的基础上完成了添加轮胎涂色的功能,但是调用方法发生了改变,以前是用car()的方法直接调用,现在变成了make_color(car),假设给汽车轮胎涂色的时候,别的车间还要给汽车安装玻璃什么的,如果把调用方式改变了,那么以前别的车间设定好的调用方式就找不到汽车了。
4、在不改变原被装饰的函数的源代码以及调用方式下,实现功能
def car():
color = 'red'
print(f'汽车的颜色:{color}')
def make_color(func):
def inner():
func()
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
return inner
car = make_color(car)
car()
#汽车的颜色:red
#汽车的轮胎颜色:green
分析:现在好了,car()的原始代码没有发生改变,而且调用方式也没有变,还为其添加了一个轮胎涂色的新功能,这个 make_color就是一个简单的装饰器,在没有改变原被装饰的函数的源代码以及调用方式下,实现了新的功能。
三、被装饰函数的返回值和参数问题
1、返回值问题
在以上1.4的装饰器中,如果每辆汽车都有一个出厂编号作为原car()函数的返回值,那么当接收返回值的时候就会接收到:None
def car():
color = 'red'
print(f'汽车的颜色:{color}')
return '汽车编号:89757'
def make_color(func):
def inner():
func()
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
return inner
car = make_color(car)
ret = car()
print(ret)
#汽车的颜色:red
#汽车的轮胎颜色:green
#None
为了解决这个问题,所以还需要优化装饰器内部函数,使得原函数返回值能正常接收,直接在inner函数内添加一个接收返回值的变量ret,然后将其返回就完事了
def car():
color = 'red'
print(f'汽车的颜色:{color}')
return '汽车编号:89757'
def make_color(func):
def inner():
ret = func()
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
return ret
return inner
car = make_color(car)
res = car()
print(res)
#汽车的颜色:red
#汽车的轮胎颜色:green
#None
2、参数问题
在以上1.4的装饰器中,如果每辆汽车都有一个出厂编号作为原car()函数的参数,那么当程序运行时会报错,为了解决这个问题,需要将出厂编号参数先传给inner,间接的传给func内:
def car(m):
color = 'red'
print(f'汽车的颜色:{color}')
return f'编号{m}'
def make_color(func):
def inner(m):
ret = func(m)
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
return ret
return inner
car = make_color(car)
ret = car(89757)
print(ret)
#汽车的颜色:red
#汽车的轮胎颜色:green
# 编号89757
四、装饰器的扩展
1、标准装饰器
def wrapper(func):
def inner(*args,**kwargs):
'''执行被装饰函数之前的操作'''
ret = func
'''执行被装饰函数之后的操作'''
return ret
return inner
2、语法糖
通过以上1.4的装饰器,每次要执行添加新功能的被装饰函数car()的时候,都要写一句car = make_color(car),如果还有别的为被装饰函数添加新功能的装饰器,很容易产生歧义,一堆的car = …堆叠在一起,很不直观,所以语法糖很好的解决了这个问题,它使得代码更加具有可读性。
def make_color(func):
def inner(m):
ret = func(m)
tyre_color = 'green'
print(f'汽车的轮胎颜色:{tyre_color}')
return ret
return inner
@make_color#语法糖
def car(m):
color = 'red'
print(f'汽车的颜色:{color}')
return f'编号{m}'
ret = car(89757)
print(ret)
#汽车的颜色:red
#汽车的轮胎颜色:green
# 编号89757
3、带参数的装饰器(高级装饰器)
1、强调这不是被修饰函数带参数,是装饰器本身要带参数
2、想要装饰器本身带参数,就要再给装饰器加外部嵌套一个函数,使用新嵌套的外部函数完成传参
例:假设汽车轮胎涂色的车间有2个,1号车间给汽车把轮胎染成绿色,2号车间给汽车把轮胎染成黑色,我们要把车间编号当做参数,然后让汽车到指定车间去给轮胎涂色:
def farm(x):
def make_color(func):
def inner(*args, **kwargs):
if x == 1:
ret = func(*args, **kwargs)
tyre_color = 'green'
print(f'汽车的轮胎染色:{tyre_color}')
print(f'编号{args[0]}的汽车在{x}号车间完成轮胎涂色')
return ret
elif x == 2:
ret = func(*args, **kwargs)
tyre_color = 'black'
print(f'汽车的轮胎染色色:{tyre_color}')
print(f'编号{args[0]}的汽车在{x}号车间完成轮胎涂色')
return ret
else:
ret = func(*args, **kwargs)
tyre_color = 'black'
print(f'汽车的轮胎染色色:{tyre_color}')
print('没有这个车间,你去火星涂色吧!!!')
return ret
return inner
return make_color
@farm(1)#语法糖
def car1(m):
color = 'red'
print(f'汽车的颜色:{color}')
return f'编号{m}'
@farm(2)
def car2(m):
color = 'yellow'
print(f'汽车的颜色:{color}')
return f'编号{m}'
@farm(3)
def car3(m):
color = 'black'
print(f'汽车的颜色:{color}')
return f'编号{m}'
car1(1111)
print('\n')
car2(2222)
print('\n')
car3(3333)
# 汽车的颜色:red
# 汽车的轮胎染色:green
# 编号1111的汽车在1号车间完成轮胎涂色
#
#
# 汽车的颜色:yellow
# 汽车的轮胎染色色:black
# 编号2222的汽车在2号车间完成轮胎涂色
#
#
# 汽车的颜色:black
# 汽车的轮胎染色色:black
# 没有这个车间,你去火星涂色吧!!!