闭包
要理解Python的装饰器,首先需要理解闭包的概念。
闭包的定义
如果在一个函数中,定义了另外一个函数,并且这个函数使用了外面函数的变量,而且外面那个函数返回了里面这个函数的引用,那么称为里面的这个函数为闭包。
def greet(name):
print("This is out")
def say_hello():
print("My name is %s" % name)
return say_hello
ret = greet("Lechrond")
ret()
对于上面的代码来说,say_hello()
就是闭包。
nonlocal关键字
如果想要在闭包中修改外面函数的变量,这时候应该使用nonlocal
关键字,来把这个变量标识为外面函数的变量:
def greet(name):
print("This is %s" % name)
def say_hello():
# 在闭包中修改外面函数的变量需要使用nonlocal关键字
nonlocal name
name += "!!!"
print("This is %s" % name)
return say_hello
greet("lechrond")()
上面代码的输出结果为:
This is lechrond
This is lechrond!!!
装饰器
装饰器利用了函数也可以作为参数传递和闭包的特性,可以让我们的函数在执行之前或者执行之后方便的添加一些代码。比如我们想要计算一个函数的执行时间,但是又不想破坏函数的内部结构,这时候就可以使用装饰器来完成这个功能。
from time import time
def cal_time(func):
def wrapper():
start_time = time()
func()
end_time = time()
print("This func cost %fs" % (end_time - start_time))
return wrapper
@cal_time
def say_something():
sum = 0
for x in range(10000):
sum += x
print(sum)
程序的输出结果为:
49995000
This func cost 0.001000s
被装饰的函数带有参数
很多时候我们需要被装饰的函数都会带有不定数量的参数,这时我们可以使用*args
和**kwargs
来传递参数,不定数量的参数解释见之前的文章。
user = {
"is_login": True
}
def login_required(func):
def wrapper(*args,**kwargs):
if user['is_login'] == True:
func(*args,**kwargs)
else:
print('跳转到登录页面')
return wrapper
@login_required
def edit_user(username):
print('用户名修改成功:%s' % username)
带参数的装饰器
装饰器也可以传递参数。但是这样的话需要在这个装饰器中写两层方法。
from functools import wraps
user = {
'is_login': False
}
def login_required(site='front'):
def outter_wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
if user['is_login'] is True:
func(*args, **kwargs)
else:
if site == 'front':
print("转跳到前端登陆页面")
else:
print("转跳到后端登陆页面")
return inner_wrapper
return outter_wrapper
@login_required('back')
def edit_info(username):
print("用户%s信息改变成功" % username)
edit_info('lechrond')
这样的写法和之前相比,主要区别就是@login_required
变成了@login_required('back')
,由于使用了()
,因此就相当于发生了函数调用,于是后者等价于@outter_wrapper
,而outter_wrapper
的作用就跟之前代码块中的login_required
功能一模一样,这样就成功做到了既给装饰器传递了参数,又实现了之前的功能。
wraps装饰器
上面的代码块中,还有一个@wraps(func)
装饰器,这个装饰器的作用主要是为了防止我们的函数失去一些属性,比如__name__
。
例如在上面的代码中,我们使用print(edit_info.__name__)
,结果会正确的输出edit_info
。
但是如果我们把@wraps(func)
注释掉,结果就会变成inner_wrapper
,也就是我们原本函数的名字丢失了,在某些情况下这可能会产生严重且难以排查的错误,所以在任何时候,都应该加上这个代码段。