13. 装饰器
一、什么是装饰器
装饰器 指的定义一个函数,该函数是用来装饰其它函数的,即为其它函数添加额外的功能。
通过装饰器和闭包,可以在不修改原来函数的情况下对函数进行扩展。在开发中,我们都是通过装饰器来扩展函数的功能。在定义函数时,我们可以通过 @装饰器
,来使用指定的装饰器装饰当前函数。
def outer(func):
"""
用来对其它函数进行扩展,使其函数可以在执行前打印开始执行,执行后打印执行结束
@param func 要扩展的函数
@return inner 返回扩展函数
"""
print("outer()开始执行!")
# 创建一个新函数
def inner(*args, **kw_args):
# 在原函数执行前扩展功能
print("inner()开始执行!")
# 调用被扩展的函数
result = func(*args, **kw_args)
# 在原函数执行后拓展功能
print("inner()执行结束!")
# 返回原函数执行结果
return result
print("outer()执行结束!")
# 返回新函数
return inner
当 Python 解释器遇到 @outer 代码时,它会将 outer 当作可执行的对象来调用,即 outer(func),并且将下面的 f() 函数指向的代码块当作实参进行传递,此时变成了 outer(f)。接下来执行 outer(func) 函数,在执行的过程中 func 变量指向了 f 指向的那个代码块。然后,将 inner 指向的代码块这个引用当作返回值返回。接下来,让 f 这个变量保存 outer(f) 的返回值。此时会让 f 指向了 inner 指向的那个函数。也就是说,f 指向变成了 outer(func) 函数的返回值。
# 使用@装饰器为函数指定装饰器
@outer
def f():
print("f()函数执行了!")
@outer 这个装饰器对 f 装饰的时间不是因为 f() 的调用,而是在 Python 解释器从上向下解析到 @outer 的时候就会执行,即开始装饰;
当执行 f() 时,f() 会调用 inner 指向的那个代码块,即接下来要执行 inner() 函数,当执行到 result = func(*args, **kw_args) 语句时,会跳转到原来 f 刚开始指向的那个函数代码块。
f()
装饰器能够让原本的函数在调用的时候变成了先调用添加的功能,然后在调用原函数的功能,即实现了未修改原代码的情况下,可以对原函数添加功能的效果。但是只能在原函数运行之前或者运行之后添加吗,不能在原函数运行一半时添加。
装饰器将原函数的引用当做实参传递到闭包中,它修改原函数指向为闭包中的内部函数;
二、通用装饰器
使用装饰器后,我们将原函数名指向的内存地址偷梁换柱成装饰器内部新创建函数(inner())的内存地址。所以,我们应该将装饰器内部创建的函数做的跟原函数一样才可以。但是上面的代码,还存在一些问题。例如运行如下代码,输入的信息不是原函数的信息而是装饰器内部创建函数的信息。
print(f.__name__)
help(f)
我们我们可以将原函数的属性手动赋值给装饰器内部创建的属性。
def outer(func):
"""
用来对其它函数进行扩展,使其函数可以在执行前打印开始执行,执行后打印执行结束
@param func 要扩展的函数
@return inner 返回扩展函数
"""
# 创建一个新函数
def inner(*args, **kw_args):
# 在原函数执行前扩展功能
print("inner()开始执行!")
# 调用被扩展的函数
result = func(*args, **kw_args)
# 在原函数执行后拓展功能
print("inner()执行结束!")
# 返回原函数执行结果
return result
# 手动将原函数的属性赋值给创建的额新函数
inner.__name__ = func.__name__
inner.__doc__ = func.__doc__
# 返回新函数
return inner
此时,我们将原函数的属性赋值给装饰器内部创建的函数,但是由于原函数有太多的属性,我们难免有一些遗漏的属性忘记赋值。为此,Python 内部提供了工具自动完成这件事。
from functools import wraps
def outer(func):
"""
用来对其它函数进行扩展,使其函数可以在执行前打印开始执行,执行后打印执行结束
@param func 要扩展的函数
@return inner 返回扩展函数
"""
# 创建一个新函数
@wraps(func)
def inner(*args, **kw_args):
# 在原函数执行前扩展功能
print("inner()开始执行!")
# 调用被扩展的函数
result = func(*args, **kw_args)
# 在原函数执行后拓展功能
print("inner()执行结束!")
# 返回原函数执行结果
return result
# 返回新函数
return inner
三、叠加多个装饰器
我们可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰。
from functools import wraps
def make_bold(func):
@wraps(func)
def wrapped(*args, **kwargs):
print("make_bold装饰器开始执行!")
result = func(*args, **kwargs)
print("make_bold装饰器执行结束!")
return "<b>" + result + "</b>"
return wrapped
def make_italic(func):
@wraps(func)
def wrapped(*args, **kwargs):
print("make_italic装饰器开始执行!")
result = func(*args, **kwargs)
print("make_italic装饰器执行结束!")
return "<i>" + result + "</i>"
return wrapped
# 装饰顺序从下往上
@make_bold
@make_italic
def test():
return "hello world!"
当 Python 解释器执行到 @make_bold 时,发现下一行不是函数,而是另一个装饰器,所以会暂停执行 @make_bold,而是让下一行的装饰器 @make_italic 先执行。
Python 解释器执行 @make_italic,发现下一行是 test() 函数,相当于执行 test = make_italic(test)。此时的形参 func 指向 test。执行完之后,test 重新指向 make_italic 代码块内的 wrapped 代码块。
然后,接着又回去执行 @make_blod,相当于执行 test = make_blod(test)。此时的形参 func 指向 test,即 make_italic 代码块内的 wrapped 代码块。执行完之后,test 又重新指向 make_bold 代码块内的 wrapped 代码块。
最终,test 指向 @make_bold 装饰器的内部函数 wrapped,而 @make_bold 装饰器的形参 func 又指向 @make_italic 装饰器的内部函数 wrapped,@make_italic 装饰器的形参 func 又指向原来的 test 指向的代码块。
# 调用顺序自上而下
print(test())
装饰顺序从下往上,调用顺序自上而下;
四、有参装饰器
由于我们使用 @装饰器
的方法,外层函数只能有一个参数,并且该参数只能用来接收被装饰对象的内存地址。我们可以在两层函数的基础上在套一层函数。
from functools import wraps
def auth(db_type="file"):
def middle(func):
@wraps(func)
def inner(*args, **kw_args):
if db_type == "file":
print("基于文件的验证")
result = func(*args,**kw_args)
return result
elif db_type == "MySQL":
print("基于MySQL数据库的验证")
result = func(*args,**kw_args)
return result
elif db_type == "SQLite":
print("基于SQLite数据库的验证")
result = func(*args,**kw_args)
return result
else:
print("不支持其它验证方式!")
result = func(*args,**kw_args)
return result
return inner
return middle
@auth(db_type="MySQL")
def index():
print("返回首页")
index()
执行 @auth(db_type="MySQL") 相当于以下步骤:
- 执行 auth("MySQL"),得到返回值 middle;
- 将上一步得到的返回值进行调用,此时会执行 middle(index),得到返回值 inner;
- 将第 2 步得到的返回值 inner 赋值给 home;
@auth(db_type="SQLite")
def home():
print("返回主页")
home()
@auth()
def showInfo(name,age):
print("name: ", name, ", age: ", age)
showInfo("Sakura",10)
五、类装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个 callable 对象作为参数,然后返回一个 callable 对象。在 Python 中,一般 callable 对象都是函数。但也有例外,只要某个对象重写了 __call__()
方法,那么这个对象就是 callable 的。
class Test:
def __init__(self, func):
print("---初始化---")
print("func name is %s" % func.__name__)
self.__func = func
def __call__(self):
print("---装饰器中的功能---")
self.__func()
@Test
def test():
print("---test---")
当用 Test 类作为装饰器对 test() 函数进行装饰的时候,首先会创建 Test 的实例对象,并且会把 test 这个函数名当做参数传递到 __init__()
方法中形参 func,即在 __init__()
中的属性 __func
指向了 test 指向的函数。test 指向了用 Test 创建出来的实例化对象。
test()
当在使用 test() 进行调用时,就相当于直接调用实例对象,因此会调用这个对象的 __call__()
方法。为了能够在 __call__()
方法中调用原来 test 指向的函数体,所以在 __init__()
方法中就需要一个实例属性来保存这个函数体的引用。所以,才有了 self.__func = func
这句代码,从而在调用 __call__()
方法中能够调用到 test 之前的函数体。
六、有参类装饰器
class Test:
def __init__(self, data):
print("__init__()方法执行")
self.__data = data
def __call__(self, func):
print("__call__()方法执行")
self.__func = func
return self.call_old_func
def call_old_func(self):
print("开始调用装饰器中的功能1")
self.__func()
print("开始调用装饰器中的功能2")
@Test(100)
def test():
print("---test---")
test()