# 这是一个装饰器的简单例子
@dec
def func():
pass
什么是装饰器
装饰器可以在不改变原有对象的代码及调用方式的情况下,为原有的对象增加新的功能或限制条件。装饰器有函数装饰器,也有类装饰器。装饰器体现的是开放封闭原则,即对功能扩展的开放,对修改已实现功能的封闭。
装饰器本质上就是Python的函数,它和普通的函数没有任何区别,只是有了特殊的用法,所以在特殊用法下就有了装饰器这个名字。
装饰器怎么用
现在有一个hello函数,功能很简单,只是打印hello world!
def hello():
print('hello world!')
hello()
它的执行结果为:
hello world!
现在要对这个函数进行一下功能的扩展。
def dec(func): # 这个就是装饰器,参数就是被装饰的函数
def wrapper():
print('start...')
func()
print('end...')
return wrapper
@dec # 这是装饰器的用法
def hello():
print('hello world!')
hello() #函数的调用方法和上边一样没有改变
现在的执行结果为:
start...
hello world!
end...
如上面的代码显示,函数dec就是装饰器,函数hello就是被装饰的函数。而装饰器的用法,就是hello函数上面的@dec。注意,这里应用装饰器只是写了dec这个函数名,而不是dec()这样调用它。
装饰器的运行机制
还是上面的例子。
- 解释器读到函数dec,也就是装饰器。这时它还没有被调用,所以只是被存在了内存中,并没有被执行。
- 到了@dec。@dec和dec()一样,都会执行这个函数,但是不同的是,@dec会把被装饰的hello函数的本身(而不是函数执行结果)当作参数,传入到dec函数中,相当于执行了dec(hello)这个函数。
- 这时开始执行dec函数。首先会把wrapper函数存入到内存中(并不是执行),然后返回wrapper函数本身(也不是执行)给hello函数。也就是,hello函数被装饰完之后,内存地址被指向到了wrapper函数的内存地址。
- 执行hello函数。由于hello函数的地址已经被指向到了wrapper函数,所以执行结果就是wrapper函数的执行结果。wrapper函数中的func这个函数地址才是原来hello函数的内存地址。所以使用装饰器之后,表面上不会改变原来函数调用的方法。
以上就是装饰器的运行机制。
为什么是嵌套函数
刚开始学习装饰器的人可能会比较疑惑,装饰器为什么要用嵌套函数?感觉好多功能不用嵌套也可以实现。下面看例子。
def dec(func):
print('start...')
func()
print('end...')
@dec
def hello():
print('hello world!')
执行结果为:
start...
hello world!
end...
这个执行结果看起来没有问题。是的,执行结果是没有问题,但是问题是还没有写执行语句却已经有了执行结果。
造成这个问题的原因就在@dec这里。@dec就是执行装饰器的语句,所以它就执行了dec这个函数。这就是为什么,还没有写执行语句却有了执行结果的原因。这也是为什么装饰器要用嵌套函数的原因,再用一个函数封装一下,避免在定义阶段就给执行了。
被装饰的函数带参数
def hello(string):
print(f'hello {string}!')
hello('python')
执行结果为:
hello python!
这是一个带参数的函数,那么这种函数如何添加装饰器呢?
其实,只要理解了被装饰的函数会被指向到装饰器里的函数,那么在装饰器里的函数加上参数就解决了传参的问题。下面看例子。
def dec(func):
def wrapper(string): # 添加和hello一样的参数即可
print('start...')
func(string) # 通过wrapper函数传递来的参数
print('end...')
return wrapper
@dec
def hello(string): # 内存地址会被指向wrapper的内存地址
print(f'hello {string}!')
hello('python')
执行的结果为:
hello python!
装饰器带参数
如果理解了前边的内容,那么理解装饰器带参数也就比较容易了。下面先看例子。
def outer(name): # 多了一层函数,用于接收传递装饰器的参数
def dec(func):
def wrapper(string):
print(f'{name} start...')
func(string)
print(f'{name} end...')
return wrapper
return dec
@outer('haha') # 装饰器执行语句多了括号和参数
def hello(string):
print(f'hello {string}!')
hello('python')
执行结果为:
haha start...
hello python!
haha end...
首先可以看到,装饰器在定义的时候又多了一层嵌套,这层嵌套用于接收传递参数,并且返回下一层的嵌套函数。
然后可以看到装饰器多了括号和参数。现在说的就是带参数的装饰器,所以带参数没有什么稀奇的。
最主要的是多的那个括号。装饰器执行语句多了括号之后,它的意思也稍微有了变化。我们知道,执行函数就是函数名加上括号。那么 @outer('haha') 的执行顺序是下面这样的。
- 先执行outer('haha')这个函数,这时它的参数是'haha'。
- outer函数返回值是dec,所以@outer('haha') = @dec,并把参数传到了函数内。
- @dec这个语句就是我们前边熟悉的装饰器的执行语句了。它会把hello函数本身当作参数传递给函数dec,并把内存地址指向到了wrapper函数。后边执行过程就跟前边的例子一样,也就不再赘述了。
主要知道@outer()会先执行outer函数,而不是把hello当作参数的装饰器语句,那么对于带参数的装饰器也就没什么难点了。
叠加装饰器
装饰器也可以叠加使用,还是先看例子。
def dec1(func):
print('这是第一层')
def wrapper():
print('这是第1层开始')
func()
print('这是第1层结束')
return wrapper
def dec2(func):
print('这是第二层')
def wrapper():
print('这是第2层开始')
func()
print('这是第2层结束')
return wrapper
@dec2
@dec1
def hello():
print('hello world!')
print('开始')
hello()
执行结果为:
这是第一层
这是第二层
开始
这是第2层开始
这是第1层开始
hello world!
这是第1层结束
这是第2层结束
这个结果可能刚开始看有些不那么好理解,我们慢慢看。
先看执行结果的前三行。这三行内容里,首先执行的是两个装饰器的内容,之后才是print语句。从这个执行结果可以看出,python是从上到下顺序执行,并在遇到装饰器执行语句的时候会自动执行装饰器函数('第几层开始'没有跟着执行是因为被封装进了函数,要执行语句才能执行)。再从首先执行了第一层又执行了第二层可以看出,遇到叠加装饰器时是从下向上执行的。
明白了装饰器是从下向上执行,那么就是下一层装饰器的函数就是它上一层装饰器的参数。
@dec2 # 相当与dec2(dec1(hello))
@dec1 # 相当于dec1(hello)
def hello():
这么看这个执行结果就好理解了。
结束
这篇文章主要是帮助初步理解装饰器和一些简单的用法,在这里就不详细说明其他更深奥的用法了(主要是我不会),那么这篇文章也就到这里了。