轻松让你理解装饰器与面向切面编程(AOP)

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1 统计函数运行时间的例子

1.1 需求

举个例子:我们经常会遇到统计某个函数的运行时间,例如该函数为如下

def my_fun(num):
	print('hello world')

我们可以这样做:

def my_fun():
    start = time.perf_counter()
    print('hello world')
    end = time.perf_counter()
    print(end - start)


my_fun()

1.2 如何推广到更多函数,进而统计运行时间

当我们突然想看另一个函数的运行时间怎么办?直接复制是可以的,当我们要看很多函数的呢?这时候直接复制显然是不够高效的,而且修改了函数中的内容,我们期望达到的效果是函数的内容不变,又可以统计运行时间。

那我们可以怎样做呢?我们可以考虑定义一个函数sf_time,然后将函数的引用传给它,然后在sf_time中调用这个函数并进行计时,这样就达到了我们不修改函数中的内容的目的。而且此时我们想看哪个函数的运行时间,就将这个函数的引用传过来就可以了。

def sf_time(func):
    start = time.perf_counter()
    func()
    end = time.perf_counter()
    print(end - start)


sf_time(my_fun)

但是,这时候我们可以发现我们修改了调用部分的代码,原来我们是这样调用的my_fun(),修改后变为了sf_time(my_fun)。这样的话,如果my_fun在N处都被调用了,那么就不得不去修改这N处的代码,甚至有时候调用的代码无法修改这个情况,此时怎么办?

1.3 最大限度不改动原来代码

如果不修改调用的代码,也就是调用my_fun()需要产生调用sf_time(my_fun)的效果。此时可以将sf_time赋值给my_fun,但是sf_time带有一个参数,参数必须统一,所以可以考虑让sf_time(my_fun)不直接产生调用效果,而是返回一个和my_fun()参数列表一致的函数,此时将sf_time(my_fun)的返回值赋值给my_fun,然后调用my_fun()的代码就不需要修改了。

def my_fun():
    print('hello world')

# 定义一个计时器,传入一个函数,返回另一个附加了计时功能的方法
def sf_time(func):

	# 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
    def wrapper():
        start = time.perf_counter()
        func()
        end = time.perf_counter()
        print(end - start)

	# 将包装后的函数返回
    return wrapper


my_fun = sf_time(my_fun)
my_fun()

此时我们只要在定义了my_fun之后,在调用my_fun之前,加上my_fun = sf_time(my_fun)就可以了,此时就可以实现计时的目标,这就是一个装饰器,看起来my_fun被sf_time装饰了,在这个例子中,函数进入和退出时需要,这就被称为一个横切面,这种编程方式就被称为面向切面编程(AOP)。与传统的编程习惯的从上往下执行方式相比,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域内,可以减少大量的重复代码。

2. python的语法糖

上面的这段代码看起来好像没法再精简了,python于是提供了语法糖来降低字符输入量

def sf_time(func):
    def wrapper():
        start = time.perf_counter()
        func()
        end = time.perf_counter()
        print(end - start)

    return wrapper


@sf_time
def my_fun():
    print('hello world')


my_fun()

重点在于@sf_time这里,这个和my_fun = sf_time(my_fun)完全等价,要统计某个函数的运行时间,就将@sf_time放到函数定义的前一行就可以了。这样看上去似乎就更加有装饰器的感觉了。

对于python有三个内置的装饰器,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法编程静态方法、类方法和类属性。
functools模块提供了两个装饰器,wraps(wrapped[, assigned][, updated])与total_ordering(cls)以后用到再作介绍吧。

总结:
装饰器的作用就是为已经存在的对象添加额外的功能,以AOP的方式横向插入一段逻辑,可以减少大量重复代码,较经典的应用有插入日志、性能测试、事务处理等。

参考:
菜鸟教程装饰器模式
通过AOP实现注解 自动计算方法的执行时间
python 装饰器 和面向切面编程(AOP)

上一篇:2021-05-24


下一篇:「arc113E」Rvom and Rsrev 题解