装饰器是一种设计模式,只不过在Python中有了语法层面的支持。
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的方法的一个包装,因此被称为装饰器。
装饰器这种设计模式比派生子类或者函数重载等方式更加灵活。 装饰器主要用于扩展一个类(方法)的功能,或者动态增加功能,动态撤销功能。因为装饰器和被装饰的对象之间没有耦合关系。
首先,我们需要学习闭包。那么什么是闭包? 闭包就是内层函数, 对外层函数(非全局)的变量的引用. 叫闭包。例如:
def fun1(x):
"闭包"
def fun2(y):
print(x+y)
return fun2
f = fun1(3) # 给外层函数传入x=3,之后的f(1),f(2),f(3)调用的时候,内层函数就会引用外层函数的变量x的值3
f(1)
f(2)
f(3)程序执行结果如下所示:
4
5
6print(f.__closure__)
函数有一个__closure属性,用来检测闭包。如果该结果不是None,那么就是闭包,否则不是闭包。
闭包返回了内层函数,而装饰器就是利用了闭包的特性。将被装饰的函数作为参数传入到闭包中,然后在闭包中对函数原来的功能可以做出更改。python提供了特殊的语法@装饰器放在函数外面即可。例如,我现在有个函数,如下所示:
def myPrint(name):
print("Hello,"+name)这时,我想给myPrint函数做一些功能上的扩展,例如,在打印Hello之前,先打印"------------------",在打印完Hello之后,也加上"------------------"。借助装饰器,我们可以这样实现该功能。
def outer(fun):
def inner(name):
print("------------------")
fun(name)
print("------------------")
return inner
@outer
def myPrint(name):
print("Hello,"+name)
myPrint("赵四")在inner函数中,我们调用了myPrint函数。不过在调用前后都加上了一行print,然后在外层函数中返回内层函数。这样,我们就使用装饰器对myPrint完成了装饰,扩展了myPrint原有的功能。
解释一下@outer的语法到底做了什么?实际上,这相当于myPrint=outer(myPrint),现在,我们不使用python中的特殊语法,来看看效果。
def outer(fun):
def inner(name):
print("------------------")
fun(name)
print("------------------")
return inner
def myPrint(name):
print("Hello,"+name)
myPrint=outer(myPrint)
myPrint("赵四")程序执行结果和使用@outer是一模一样的。现在,我们再来看一个例子来加深一下印象。
def outer(f):
def inner(x, y):
res = f(x, y) + 100
return res
return inner
@outer # 相当于outer(func)
def func(x, y):
return x + y
res = func(1, 2)
print(res)这个例子中原本的func函数是将他们相加起来,但是经过装饰之后,值会增加100。另外需要注意的是,上一个例子中的inner函数中没有return语句,实际上,由于不知道原来的函数是否有返回值,通常应该写上return语句。因此,第二个例子是我们在编写装饰器的时候通常使用的模板。
最后,还有一地方可以优化,那就是刚才的装饰器装饰的函数的参数是固定的,现在我们继续进程改造,让其可以装饰任意的函数。
def outer(f):
def inner(*args, **kwargs):
print("开始装饰...")
res = f(*args, **kwargs)
print("装饰结束")
return res
return inner
@outer
def fun1(name):
print(name)
@outer
def fun2(x, y):
return x + y
print(fun1("赵四"))
print("------------------------分割线----------------------")
print(fun2(1, 1))只需要给装饰器的内层函数设置*args,**kwargs参数以及将这两个参数传入外层函数的参数中即可。
上面的例子都是没有参数的装饰器,装饰器本身也是可以有参数的。例如:
def outer(x):
def outwrapper(fun):
def wrapper(*args, **kwargs):
print(F"—————————{x}——————————")
res = fun(*args, **kwargs)
print(F"—————————{x}——————————")
return res
return wrapper
return outwrapper
@outer(123) # 相当于myPrint = outer(123)(myPrint)
def myPrint(name, age):
print("name:", name)
print("age:", age)
myPrint("赵四", 18)需要注意,带参数的装饰器@outer(123)实际上相当于myPrint = outer(123)(myPrint), 因此需要三层函数嵌套才可以。这样,装饰器中可以传入参数,先形成一个完整的装饰器,然后再来装饰函数。
装饰器也可以是一个类。这个方式实际上是运算符重载带来的效果,使用__call__来重载调用表达式()既可以实现装饰器类。例如:
class Outer():
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("_________________________")
res = self.func(*args, **kwargs)
print("_________________________")
return res
@Outer
def hello(a, b, c):
print(a, b, c)
hello("hello,","good","morning")同样,也是可以带上参数的装饰器类,这比起带参数的装饰器函数更加简单。
class Outer():
def __init__(self, x):
self.x = x
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"___________{self.x}___________")
res = func(*args, **kwargs)
print(f"___________{self.x}___________")
return res
return wrapper
@Outer(666)
def hello(a, b, c):
print(a, b, c)
hello('111', '222', '333')