我没有学过太多语言,所以python的装饰器是我见过的最灵活的。—— 松言松语
装饰器
装饰器的前提
顾名思义,装饰类就是用一个装饰函数a来修饰另外一个函数b,使得函数b的功能得到增强。
在java(编译型语言)中,增强一个函数可以通过AOP实现,有两种方式:
1. 编译期,是通过修改字节码实现,如AspectJ;
2. 运行期,是通过生成一个代理对象实现的,如Spring AOP;
python是通过闭包来做的。
闭包:
一个函数(就叫它外函数吧)里面定义了一个函数(称之为内函数),内函数中用使用到了外函数中的变量,并且,外函数直接返回了这个内函数的引用
简单来说,就是 函数套函数,函数返回函数 ,举个例子
def f1(x, y):
    result = x + y
    def f2():
        print(result)
        return result
    return f2
y = f1(1, 2)
y()
# output : 
可以看到f1中“套了”一个f2,f2用到了f1中的变量,f1返回了f2。这就是闭包。理解这个,下面再来看一下python的装饰器。
装饰器函数
from functools import wraps
def decorator(func):
    """定义一个装饰器函数"""
    @wraps(func)
    def decoration_function(*args, **kwargs):
        print("start...")
        func(*args, **kwargs)
        print("end...")
    return decoration_function;
@decorator
def test():
    """被装饰的方法"""
    print("running")
test()
一共有三个函数,一个是被装饰的函数(test),外函数(decorator)、内函数(decoration_function);
内函数用
@wraps(被修饰函数)来修饰。其中,func就是被修饰的函数的变量表示;被修饰的函数用
@外函数修饰如,@decorator;被装饰的函数(test)作为外函数(decorator)的一个参数(func),传给内函数(decoration_function)并在内函数中执行;
外函数(decorator)返回内函数(decoration_function)的引用;
总结一下步骤,就是3-2法则:
3函数。函数套函数,外函数返回内函数;
2注解。
@wraps(被修饰函数)、@外函数;
需要提醒一下的是,
@decorator是一个语法糖,只是为了简化操作,从而看起来更像一个装饰器@decorator def test(): """被装饰的方法""" print("running") test()等价于
def test(): """被装饰的方法""" print("running") y = decorator(test) y()实际上,就是把被装饰函数test作为参数传到装饰器里面去。
@wraps的作用,是为了维持被装饰函数(test)执行的时候,使其函数性质保持不变。正因为
@decorator是语法糖,所以执行的时候可能存在一些问题。def decorator(func): """定义一个装饰器函数""" def decoration_function(*args, **kwargs): print("start...") func(*args, **kwargs) print("end...") return decoration_function @decorator def test(): """被装饰的方法""" print("running") print(test.__name__) # output : decoration_function去掉
@wraps(func)执行,你会发现 被装饰函数 test 其实就是闭包里返回的内函数decoration_function嘛(其实这也反向证明了@decorator是语法糖),但我们期望的输出应该是test,这不科学!test的名字、属性等都变了(虽然执行结果一样),这还能叫装饰器吗?叫做代理可能会更合理一些。
所以python中提供了
@wraps(被修饰函数),是为了声明这是一个修饰器的内函数,并复制了被修饰函数原有的名字、属性等。
装饰器类
装饰器还可以以class的形式构建。
如下所示
from functools import wraps
import time
class TimeWrapper: # 计算一个方法的执行时间 
    def __init__(self, name):
        self.name = name
    def __call__(self, func):
    """
        提供__call__的原因,是把TimeWrapper变成一个可直接执行的类;
        调用类的时候,会触发__call__中的方法,参数也是通过__call__传进来;
    """
            @wraps(func)
            def wrapped_function(*args, **kwargs):
                print('start....')
                start_time = time.perf_counter()
                result = func(*args, **kwargs)
                print('end....')
                print(f'运行时间:{time.perf_counter() - start_time:8f}s')
                return result
            return wrapped_function
# 通过注解的形式来调用装饰器类TimeWrapper,注意,此时,默认调用的是类中的__call__
@TimeWrapper('looping')
def loop(n):
    for i in range(1, n):
        print(i)
loop(4)
要点是:
- 要在类中定义一个
__call__函数,以确保类是可执行的,调用的时候,自动执行__call__函数; __call__作为外函数存在;- 使用修饰器的时候,用
@类名;