源起
装饰器的出现是为了能在Python中优雅的实现AOP编程。正如Java中要实现AOP编程需要动态代理,而Python中实现AOP只需要优雅的装饰器。
实现
装饰器的本质是闭包,装饰器可以基于函数实现,也可以基于类实现
def log_info(func):
def print_log():
print("日志开始记录")
func()
print("日志停止记录")
return print_log
闭包:内函数引用了外函数的变量,外函数的返回值是内函数的引用。
原理
# 装饰器
def log_info(func):
def print_log(name):
print("日志开始记录")
func(name)
print("日志停止记录")
return print_log
# 被装饰的函数
def print_hello(name):
print("你好 " + name)
# 本质
print_hello = log_info(print_hello)
print_hello("pandaer")
# output:
日志开始记录
你好 pandaer
日志停止记录
利用函数是对象这一特性,将内层函数print_log
的引用赋值给了 print_hello
同时覆盖了之前的print_hello
所引用的函数对象。于是就有了程序运行的结果.
上面的代码块其实就是装饰器的本质,而在python中给予了他优雅的语法糖
# 装饰器
def log_info(func):
def print_log(name):
print("日志开始记录")
func(name)
print("日志停止记录")
return print_log
@log_info # 注意这里
def print_hello(name):
print("你好 " + name)
print_hello("pandaer")
这段代码和上面那段代码等价。
传参
根据上面的原理,我们便可以知道,我们真实调用的函数其实是print_log
,所以进行参数的传递,就可以在内函数上定义形参。但是如果装饰器要修饰不同的函数呢?并且函数的参数个数还不一样呢?应对这个问题,我们可以使用python中的可变参数(可变位置形参,可变关键字形参)
# 装饰器
def log_info(func):
def print_log(*arg, **kwargs): # 这里
print("日志开始记录")
func(*arg, **kwargs) # 这里
print("日志停止记录")
return print_log
@log_info
def print_hello(name):
print("你好 " + name)
print_hello("pandaer")
嵌套
装饰器的能力:在不改变原有函数代码的前提下,扩展函数的功能。
如果想对一个函数实现多个功能的扩展,就需要使用装饰器的嵌套。
def func1(func):
def log(*args, **kwargs):
print("func1")
func(*args, **kwargs)
return log
def func2(func):
def log(*args, **kwargs):
print("func2")
func(*args, **kwargs)
return log
def func3(func):
def log(*args, **kwargs):
print("func3")
func(*args, **kwargs)
return log
@func3
@func2
@func1
def print_hello(name):
print("你好 " + name)
print_hello("pandaer")
#output:
func3
func2
func1
你好 pandaer
等价于:func3(func2(func1(print_hello)))("pandaer")
上面所谈论的就是装饰器核心的内容。再次补充一下装饰器和闭包的关系。
装饰器闭包的一种:一个接受函数类型的参数的闭包就是装饰器。
基于类的装饰器
因为装饰器的本质是闭包,而闭包本质上是一个函数,而函数在python中是一个对象。所以理论上基于类的装饰器是可行的。
# 基于类的装饰器
class Test:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("测试开始")
self.func(*args, **kwargs)
print("测试结束")
@Test
def print_hello(name):
print(f"hello,{name}, this is my home")
print_hello("pandaer")
@Test
等价于 print_hello = Test(print_hello)
因为实现了__call__
这个方法,所以使得Test的对象是可以被调用的,于是print_hello就是可以调用的了。
以上就是装饰器全部的核心知识了。不过有个小点,需要大家注意一下,装饰器的本质是闭包,所以原函数的元信息其实会被改变。
def log_info(func):
def print_log(*arg, **kwargs):
print("日志开始记录")
func(*arg, **kwargs)
print("日志停止记录")
return print_log
@log_info
def print_hello(name):
print("你好 " + name)
print(print_hello.__name__)
# output:
print_log
如果我们保留原函数的元信息,可以在内函数上,加上一个装饰器
def log_info(func):
@functools.wraps(func) # 这里
def print_log(*arg, **kwargs):
print("日志开始记录")
func(*arg, **kwargs)
print("日志停止记录")
return print_log
# output:
print_hello
应用
- 数据正确性,完整性校验
- 身份认证
- 日志记录