装饰器是python学习中的一个难点。自己写代码不一定要用,但是看别人代码时,不会装饰器往往就会卡主,所以还是有必要学习一下。
目录
定义
装饰器指的是对函数(或类)进行包装,在不修改其定义的情况下,修改其行为。
python中的函数有两个特性:
一是函数可以作为参数传递给另一个函数;
二是可以在函数内定义另一个函数。另外,如果内部函数引用了外部函数的变量,那么即使在外部函数执行完毕后仍然可以访问和修改外部函数的变量。这种现象称为闭包。
装饰器的基础就是函数以上的两种特性。
实际上装饰器,并不一定要写成@开头的语法糖形式。用一个函数包装另一个函数也是装饰器。
语法糖形式
为了代码更简洁、增加可读性和易维护,往往使用语法糖形式。
下面是一个简单的不带参数的装饰器的例子:
def my_decoder(func):
def wrapper():
print("这是一个装饰器")
func()
return wrapper
@my_decoder
def my_func():
print("ok")
if __name__=="__main__":
test=my_func()
这种语法糖的写法等价于
def my_decoder(func):
def wrapper():
print("这是一个装饰器")
func()
return wrapper
def my_func():
print("ok")
if __name__=="__main__":
my_func=my_decoder(my_func)
test=my_func()
其中@my_decoder就是装饰器。简洁之处在于,我们在实例化使用时完全不用考虑装饰器。而且装饰器的包装行为被上移到了定义函数的部分,不需要翻来翻去寻找函数之间的关系。
装饰器必须是一个可被调用的对象或者属性描述符。
被装饰函数带参数
上面的例子里被装饰的函数是不带参数的,如果想要给函数输入参数,只修改被修饰函数是不行的。如下的代码会出问题
def my_decoder(func):
def wrapper():
print("这是一个装饰器")
func()
return wrapper
@my_decoder
def my_func(x):
y=x*2
print(y)
if __name__=="__main__":
test=my_func(3)
这样的代码会报错如下
这里的问题是,因为装饰的过程里已经把my_func()给替换成my_decoder(func)了,func()被包装在了wrapper中。wrapper()不带参数,因此,替换后的函数是不带参数的。
因此,想要写带参数的装饰器代码,不仅要修改被装饰函数,也要修改装饰器。
如下:
def my_decoder(func):
def wrapper(x):
print("这是一个装饰器")
func(x)
return wrapper
@my_decoder
def my_func(x):
y=x*2
print(y)
if __name__=="__main__":
test=my_func(3)
带可变数量参数以及降低耦合度
上一节中装饰器需要知道业务函数具体的参数个数,长度固定,且导致装饰器和业务函数高度绑定。
将装饰器具体的参数替换成(*args,**kwargs)可以允许可变长度参数并降低耦合度
def my_decoder(func):
def wrapper(*args,**kwargs):
print("这是一个装饰器")
func(*args,**kwargs)
return wrapper
@my_decoder
def my_func1(x):
y=x*2
print(y)
@my_decoder
def my_func2(x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_func1(3)
test=my_func2(3,7)
运行结果:
装饰器本身带参数
只要给装饰器再多包装一层,并把内层装饰器作为返回值,就可以实现。
def my_decoder(number):
def inner_decoder(func):
def wrapper(*args,**kwargs):
print("这是一个装饰器")
func(*args,**kwargs)
print(number)
return wrapper
return inner_decoder
@my_decoder(2)
def my_func2(x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_func2(3,7)
结果
可以用装饰器参数和被装饰函数的参数进行运算
def my_decoder(number):
def inner_decoder(func):
def wrapper(*args,**kwargs):
print("这是一个装饰器")
func(*args,**kwargs)
result=number+args[0]
print(result)
return wrapper
return inner_decoder
@my_decoder(2)
def my_func2(x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_func2(3,7)
装饰器修改被装饰函数内的计算公式
仅仅只是增加一些输出功能在实际运用中并不实用,我们可能需要直接修改被装饰函数内部的计算公式。例子如下:
def modify_formula(func):
def wrapper(*args, **kwargs):
# 修改运算公式
result = func(args[0], kwargs['multiplier']) * kwargs['multiplier']
return result
return wrapper
@modify_formula
def calculate(x, multiplier=1):
return x ** 2 + x + 1 # 原始运算公式为 x 的平方加 x 加 1
# 调用被装饰函数,并传递额外的参数
print(calculate(5, multiplier=3))
装饰器装饰类
其实跟装饰函数是一样的。
def my_decoder(cls):
def wrapper(*args,**kwargs):
print("这是一个装饰器")
cls(*args,**kwargs)
return wrapper
@my_decoder
class my_cls(object):
def __init__(self,x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_cls(3,7)
类作为装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
想要用类作为装饰器,就需要__call__()
方法。
我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class my_decoder(object):
def __init__(self,func):
self.func=func
def __call__(self,*args,**kwargs):
print("这是一个装饰器。")
return self.func(*args,**kwargs)
@my_decoder
class my_cls(object):
def __init__(self,x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_cls(3,7)
带参数的类装饰器
和函数做装饰器时一样,需要多包装一层,但是更复杂一些。
另外需要注意,装饰器内需要执行的语句都要放在__call__()方法内。
于是,实现代码如下:
class my_decoder(object):
def __init__(self,a):
self.a=a
def __call__(self,func):
print(self.a)
def wrapper(*args,**kwargs):
print(self.a)
print("这是一个装饰器。")
return func(*args,**kwargs)
return wrapper
@my_decoder("x")
class my_cls(object):
def __init__(self,x,y):
z=x+y
print(z)
if __name__=="__main__":
test=my_cls(3,7)
结果:
多个装饰器的运行顺序
如下
def dec1(func):
def wrapper(*args, **kwargs):
print("dec1 before")
func(*args, **kwargs)
print("dec1 after")
return wrapper
def dec2(func):
def wrapper(*args, **kwargs):
print("dec2 before")
func(*args, **kwargs)
print("dec2 after")
return wrapper
@dec1
@dec2
def my_function():
print("my_function")
my_function()
运行结果