前言
python中的装饰器可以说是一种高级语法了,是对闭包的一种封装。所谓闭包,用通俗的话来讲,定义一个函数,在函数内部再定义一个函数,内部定义的函数使用了外部函数的变量,同时返回值是内部函数。
闭包
闭包示例:
def test(num):
def test_in(x):
y = x + num
print(y)
return test_in
if __name__ == '__main__':
ret = test(1)
print(ret)
内部函数对外部函数变量(非全局变量)的引用,则称内部函数为闭包。闭包优化了变量,但是由于闭包引用了外部函数的局部变量,外部函数的局部变量没有及时释放,消耗内存。
假设这样一个业务场景,f1函数需要验证才能调用,为了遵循开放封闭原则,不能对f1函数进行改动,比较好的一个方法就是另外写一个验证函数,这个验证函数传入的参数就是所要调用的函数名。
def authority(func):
def inner():
print('正在进行验证')
# 此处可加验证函数,这里略过不写
func()
return inner
def f1():
print('----1----')
if __name__ == '__main__':
f1 = authority(f1) #传入所要使用的函数名
f1()
结果
正在进行验证
----1----
装饰器
def authority(func):
def inner():
print('正在进行验证')
func()
return inner
@authority #装饰器
def f1():
print('----1----')
if __name__ == '__main__':
f1()
结果
正在进行验证
----1----
可以发现,闭包和装饰器达到的效果是一样的,装饰器是闭包的一种封装效果,称为‘’语法糖‘’。
装饰器的执行顺序以及被装饰的函数带有传参
下面的示例是将hello world装饰成<html><head>hello world</head></html>,对一个函数运用了两次装饰器。
def head(func):
print('正在进行head装饰')
def inner1(args): #这里用来接收函数的传参
new_args = '<head>' + args + '</head>'
func(new_args)
return inner1
def html(func):
print('正在进行html装饰')
def inner2(args):
new_args = '<html>' + args + '</html>'
func(new_args)
return inner2
@head
@html
def hello(body):
print(body)
if __name__ == '__main__':
hello('hello world')
结果
正在进行html装饰
正在进行head装饰
<html><head>hello world</head></html>
总的来说,两个装饰器在执行时,是先执行后装饰的函数,即先装饰html,后装饰head,但是print的顺序是正序的。
装饰函数的返回值
def good_output(fun_name):
def wrapper(*args, **kwargs): #这里接收所调用函数的传参
ret = fun_name(args[0], args[1]) # 函数的返回值
new_ret = '%s + %s = %s'%(args[0], args[1], ret)
return new_ret
return wrapper
@good_output
def input_num(a, b):
return a+b
if __name__ == '__main__':
result = input_num(1, 2)
print(result)
结果
1 + 2 = 3
装饰器带有传参
def print_num(num):
def func(fun_name):
def wrapper():
if 'two' == num:
fun_name()
fun_name()
if 'one' == num:
fun_name()
return wrapper
return func
@print_num('one')
def hello_world():
print(hello_world.__name__)
@print_num('two')
def hello_china():
print(hello_china.__name__)
if __name__ == '__main__':
hello_world()
hello_china()
结果:
wrapper
wrapper
wrapper
装饰器的传参可以有不同的用处,这里的示例仅作为一个打印数量的选择,但是有一个问题,hello_world.__name__打印的应该是函数的名称,由于这里使用了闭包,函数的名称变成了wrapper,这个问题可以用python自带的库解决。
functools.wraps
from functools import wraps
def print_num(num):
def func(fun_name):
@wraps(fun_name) #保持装饰的函数的 __name__ 的值不变
def wrapper():
if 'two' == num:
fun_name()
fun_name()
if 'one' == num:
fun_name()
return wrapper
return func
@print_num('one')
def hello_world():
print(hello_world.__name__)
@print_num('two')
def hello_china():
print(hello_china.__name__)
if __name__ == '__main__':
hello_world()
hello_china()
结果
hello_world
hello_china
hello_china