使用场景:给某个已有的功能函数或功能类增加功能,复用已有的功能函数或功能类。
原理:使用一个闭包函数或重新定义__init__()、__call__()方法的类作为装饰器。
一、装饰器入门
1、单装饰器
def extra_func1():
print("I am extra_func1")
def fun_out1(func):
def func_in():
extra_func1()
func()
return None
return func_in
@fun_out1
def foo():
print("I am foo")
return None
if __name__ == '__main__':
foo()
"""
执行结果:
I am extra_func1
I am foo
Process finished with exit code 0
"""
2、多装饰器
def extra_func1():
print("I am extra_func1")
def fun_out1(func):
def func_in():
extra_func1()
func()
return None
return func_in
def extra_func2():
print("I am extra_func2")
def fun_out2(func):
def func_in():
extra_func2()
func()
return None
return func_in
@fun_out1
@fun_out2
def foo():
print("I am foo")
return None
if __name__ == '__main__':
foo()
"""
执行结果:
I am extra_func1
I am extra_func2
I am foo
Process finished with exit code 0
"""
3、通用装饰器
import time
def write_log(func):
print(f"访问方法:", func.__name__, "\t时间:", time.asctime())
def func_out(func):
def func_in(*args, **kwargs): # *args、**kwargs此处作文形参
write_log(func)
return func(*args, **kwargs) # *args、**kwargs此处作为实参
return func_in
"""
@func_out 解析:
相当于 add1 = func_out(add1), add1指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后add1的一些属性会改变为闭包的内部函数属性,如:add1.__name__、add1.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用add1实际上调用的闭包内部函数
"""
@func_out
def add1(a, b):
return a + b
@func_out
def add2(a, b, c):
return a + b + c
if __name__ == '__main__':
print("两个数的和为:", add1(1, 2))
print("三个数的和为:", add2(1, 2, 3))
"""
执行结果:
访问方法: add1 时间: Sat Jun 20 11:11:15 2020
两个数的和为: 3
访问方法们: add2 时间: Sat Jun 20 11:11:15 2020
三个数的和为: 6
Process finished with exit code 0
"""
二、四类形式不同的装饰器
1、函数装饰函数
"""
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了
(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫
wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加
上functools的wrap,它能保留原有函数的名称和docstring。
"""
import time
from functools import wraps
def write_log(func):
print("访问方法:", func.__name__, "\t时间:", time.asctime())
def func_out(func):
"""
功能:在add(a, b)执行前后添加额外功能。
"""
@wraps(func)
def func_in(a, b):
write_log(func)
return func(a, b)
return func_in
"""
@func_out 解析:
相当于 add = func_out(add), add指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后add的一些属性会改变为闭包的内部函数属性,如:add.__name__、add.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用add实际上调用的闭包内部函数
"""
@func_out
def add(a, b):
return a + b
if __name__ == '__main__':
print("两个数的和为:", add(1, 2))
print(add.__name__)
"""
运行结果:
访问方法: add 时间: Sat Jun 20 11:43:28 2020
两个数的和为: 3
add
Process finished with exit code 0
"""
2、函数装饰类
import time
from functools import wraps
def write_log(cls):
print("准备创建的实例对象所属类:", cls.__name__, "\t创建时间:", time.asctime())
def func_out(cls):
"""
功能:在实例对象创建前或后添加功能
"""
@wraps(cls)
def func_in(*args, **kwargs): # *args, **kwargs ,此处作为形参,接收Add(1, 2)的参数,此处没有参数
write_log(cls)
return cls(*args, **kwargs) # *args, **kwargs ,此处作为实参,传递给Add类中的__new__和__init__方法的参数
return func_in
"""
@func_out 解析:
相当于 Add = func_out(Add), add1指向转变为闭包的内部函数。这也解释了:
(1)为什么加上该语句后Add的一些属性会改变为闭包的内部函数属性,如:Add.__name__、Add.__doc__,往往需要在闭包内部函数上加上@warp装饰器;
(2)为什么闭包外部函数只会执行一次;
(3)后期调用Add实际上调用的闭包内部函数
"""
@func_out
class Add(object):
def __init__(self):
pass
def add(self, a, b):
return a + b
if __name__ == '__main__':
add = Add()
print("两个数的和为:", add.add(1, 2))
"""
运行结果:
准备创建的实例对象所属类: Add 创建时间: Sat Jun 20 12:30:08 2020
两个数的和为: 3
Process finished with exit code 0
"""
该方法可用于实现单例模式。
3、类装饰函数
import time
def write_log(func):
print(f"访问方法:", func.__name__, "\t时间:", time.asctime())
class Decorator(object):
"""
功能:在add(a, b)执行前后添加额外功能。
"""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs): # __call__(self, a, b) 也可以
write_log(self.func)
return self.func(*args, **kwargs) # self.func(a,b)
"""
@Decorator 解析:
相当于 add = Decorator(add), add指向转变为所创建的Decorator可调用实例对象。这也解释了:
(1)为什么加上该语句后add的一些属性会改变为所创建的Decorator可调用实例对象属性,如:出现了print(add.__class__)为Decorator;
(2)为什么Decorator.__init__只会执行一次;
(3)后期调用add实际上调用的该add实例所绑定的__call__方法。
"""
@Decorator
def add(a, b):
return a + b
if __name__ == '__main__':
print(type(add))
print("两个数的和为:", add(1, 2))
"""
运行结果:
<class '__main__.Decorator'>
访问方法: add 时间: Wed Jul 8 12:55:31 2020
两个数的和为: 3
Process finished with exit code 0
"""
4、类装饰类
import time
def write_log(cls):
print("准备创建的实例对象所属类:", cls.__name__, "\t创建时间:", time.asctime())
class Decorator(object):
"""
功能:在实例对象创建前或后添加功能
"""
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs): # *args, **kwargs ,此处作为形参,接收Add(1, 2)的参数,此处没有参数
write_log(self.cls)
return self.cls(*args, **kwargs) # *args, **kwargs ,此处作为实参,传递给Add类中的__new__和__init__方法的参数
"""
@Decorator 解析:
相当于 Add = Decorator(Add), Add指向转变为所创建的Decorator可调用实例对象。这也解释了:
(1)为什么加上该语句后Add的一些属性会改变为所创建的Decorator可调用实例对象属性,如:出现了print(Add.__class__)为Decorator;
(2)为什么Decorator.__init__只会执行一次;
(3)后期调用Add实际上调用的该Add实例所绑定的__call__方法。
"""
@Decorator
class Add(object):
def __init__(self):
pass
def add(self, a, b):
return a + b
if __name__ == '__main__':
add = Add()
print("两个数的和为:", add.add(1, 2))
"""
运行结果:
准备创建的实例对象所属类: Add 创建时间: Sat Jun 20 17:02:59 2020
两个数的和为: 3
Process finished with exit code 0
"""
该方法可用于实现单例模式。
5、总结
(1)解释器执行时的传参过程
装饰函数 | 装饰类 | |
---|---|---|
功能函数 | 功能函数函数名→装饰函数的外函数参数,功能函数参数→装饰函数的内函数参数。例如:1 | 功能函数函数名→装饰类__init__(self)的参数,功能函数参数→装饰类__call(self)__的参数。例如:2 |
功能类 | 功能类类名→装饰函数的外函数参数,功能类参数→装饰函数的内函数参数。例如:3 | 功能类类名→装饰类的__init__(self)的参数,功能类参数→装饰类__call(self)__的参数。例如:4 |
(2)装饰函数、装饰类的功能
装饰函数、装饰类可以在功能函数执行前后增加额外功能。
装饰函数、装饰类可以在功能类的实例对象产生前后增加额外功能。
(3)原理
装饰函数的原理是闭包函数。
装饰类:重新定义__init__()、__call__()方法的类。