装饰器
理解前提:
1. 函数可以作为参数进行传递
2. 函数可以直接被返回
3. 函数可以被赋值给变量
4. 函数内可以再次定义函数
初步认识
为了方便理解,先从举例开始,首先定义了一个函数:
函数功能:输出一个字符串。然而,老大提出需求,在不改变原函数的功能(代码)前提下对输出的字符串进行标签处理,这里有一个最直观的方案是:
定义函数decorator,接收一个函数作为参数;定义内部函数wrapper,引用外部函数的参数并执行,然后对结果进行加工,最后decorator函数将内部函数返回
调用decorator函数,传入需要处理的hello_world函数,将返回值赋值给变量after,然后执行after(),得到最终结果,完成需求
这里讲文章开始提到的几点前提全部涉及到了,所以比较关键的点是必须理解前提中说到的几点
这里需要提一点,看下图:
这里打印的函数名既不是after,也不是hello,而是wrapper。至于为什么会这样?如何解决?后文给出方案
上面我们整理下是如何处理的,完整代码:
从代码可以看出,decorator函数『装饰』了函数hello_world,所以用python专业术语来说,decorator函数就是一个装饰器,装饰器decorator装饰了hello_world函数后,返回给一个变量(本质上是一个函数)
上面的代码,用python化的写法是:
@是python提供的语法糖,非常便捷好用
初步总结就是:可以动态修改函数(或类)功能的函数就是装饰器
使用形式
经过上面一个简单例子,可以了解到,装饰器的使用形式是:
等同于:
一个函数,可以被多个装饰器进行装饰:
等同于:
函数(或类)都会有参数,装饰器本质也是函数(或类),如:
等同于:
对带参函数进行装饰
使用上面的例子,如下:
可以看到,hello_world传入的参数coco产生了作用
从上面的例子可以简单总结:装饰器内部函数作为参数接收方,将参数传递给外部函数参数func,也就是说,内部函数的参数需要和被装饰的函数参数对应!至于为什么使用*args,**kwargs,请自行了解
带参数的装饰器
这里有一个新需求,需要更换添加的标签,应该如何处理?直接修改装饰器的内部的代码显然不合理,但是不改代码好像不太好实现,这里需要对装饰器代码重构一下,如下:
在原decorator函数的外层,添加了一个带参的方法,改方法返回了decorator函数,并修改了装饰函数
简单理解就是:在装饰器外层添加一层包装,根据不同的参数返回不同的装饰器
基于类的装饰器
上面说的都是基于函数的装饰器,其实类也可以做装饰器,请看:
类中有两个关键方法:
__init__:接收函数作为初始化参数
__call__:接收被装饰函数的参数
类装饰器带参数:
和上面对比发现:如果类装饰器有参数,则__call__方法接收被装饰函数
装饰器的副作用
上面提到过,被装饰的函数,它的函数名已经不是原来的名称。
第一个例子提到:
hello_world被装饰之后,打印了函数名,名称变成了wrapper。
为了消除副作用,Python有一个自带的functools模块中,提供了名为wraps的装饰器:
上例可以看出,hello_world还是原来的名称
小结
装饰器就是将函数作为参数并且返回函数的高阶函数
装饰器可以动态的扩展函数功能,所以在代码的延展性方面装饰器使用比较广泛
装饰器其实就是闭包的一种应用(闭包:内部函数引用了外部函数的相关参数和自由变量,该返回的内部函数就是闭包)