Python核心基础~装饰器

101

源起

装饰器的出现是为了能在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

应用

  1. 数据正确性,完整性校验
  2. 身份认证
  3. 日志记录