学点简单的Python之Python装饰器与闭包
大家好,我叫亓官劼(qí guān jié )
装饰器是Python一个非常重要的部分,是开发中经常会用到的一个功能,其实在我们之前的Flask的教程中就用到了不少的装饰器,例如我们的登录状态验证,权限验证等功能。
Python闭包
说到Python装饰器,我们就不得不说到闭包了。这里所说的闭包与其他几个语言的闭包的意思是一样的,即我们在函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。简单来说,就是在函数内定义函数,且函数内部定义的函数中使用了外部函数中的变量,且内部函数可以单独的运行。
这里用语言来描述还是比较的绕,我们可以通过下面这个小例子来更直观的理解:
# 定义一个函数extern_func
def extern_func():
# 在extern_func函数内定义一个空列表list
list = []
# 在extern_func函数内定义一个函数inner_func
def inner_func(name):
# inner_func函数的功能是在list列表中添加一个name,然后输出list
list.append(name)
print(list)
# exten_func函数的返回值是inner_func的函数体
return inner_func
# 调用extern_func函数,返回值赋值给ret1,即ret1为inner_func函数体
ret1 = extern_func()
ret1('zhangsan')# 调用ret1,在list中添加一个'zhangsan',并输出list
ret1('lisi')# 调用ret1,在list中添加一个'lisi',并输出list
# 调用extern_func函数,返回值赋值给ret2,即ret2为inner_func函数体
ret2 = extern_func()
ret2('wangwu')# 调用ret2,在list中添加一个'wangwu',并输出list
ret1('qiguanjie')# 调用ret1,在list中添加一个'lisi',并输出list
输出为:
['zhangsan']
['zhangsan', 'lisi']
['wangwu']
['zhangsan', 'lisi', 'qiguanjie']
我们发现ret1和ret2中虽然都是在list中添加一个name并返回,但是ret1和ret2是两次调用extern_func( )
返回的函数体,他们作用的list是不同的,他们作用的list
可以脱离原函数extern_func()
而单独使用。这就是函数闭包的简单使用。简而言之就是我们在函数中定义函数,并且使用了外部函数中的部分变量,我们内部的函数在脱离外部函数之后继续执行,且单独作用于外部函数的部分变量。
闭包一个常见错误
我们看下面这个例子:
def func():
list = []
for i in range(3):
def inner_func():
return i*i
list.append(inner_func)
return list
re1,re2,re3= func()
print(re1())
print(re2())
print(re3())
大家是不是以为三个输出的结果应该是0,1,4
?但实际上输出的结果都是4,这是为什么呢?这里我们的i对于inner_func来说是一个外部函数,我们在list中添加是是inner_func的函数体,里面返回的是i*i
,但是当我们i变化到2之后,我们才返回list,所以我们输出的三个值才都是4,那如何避免这种情况呢?
第一种方法,继续使用闭包,但是这里需要注意我们变量的区分:
def func():
list = []
for i in range(3):
def inner_func(_i = i):
return _i*_i
list.append(inner_func)
return list
re1,re2,re3= func()
print(re1())
print(re2())
print(re3())
这里我们在inner_func
的参数中定义_i
变量,值为当前的i
,这时我们将函数体加入list
中,就可以保证值不受i
值变化的影响,输出为0,1,4。另一种方法就是我们直接在list
中存放计算好的值,而不是函数体(这种方法有一定的局限性,和之前的方法就是两种完全不同的方法了):
def func():
list = []
for i in range(3):
def inner_func():
return i*i
list.append(inner_func())
return list
re1,re2,re3= func()
print(re1())
print(re2())
print(re3())
这里我们这里添加到list中的是inner_func()
,一旦有()
则表明我们这个函数已经运行了,返回的是一个值。
nonlocal关键字
那如果我们要在内部函数中使用外部函数中的变量,并进行修改我们应该如何做呢?
def extern_func(name):
print('exter_func name is : %s' % name)
def inner_func():
name = 'inner_func ' + name
print('inner_func name is : %s' % name)
return inner_func
ret = extern_func('qiguanjie')()
如何我们直接修改的话,我们会发现这里编译会报错:UnboundLocalError: local variable 'name' referenced before assignment
这里报错的意思即我们这里的name
变量在使用前未被分配,这就和我们在函数内使用全局变量时要使用globle
关键字一样,这里在内部函数中要使用并更改外部函数中的变量,我们需要使用关键字nonlocal
,对程序进行修改:
def extern_func(name):
print('exter_func name is : %s' % name)
def inner_func():
nonlocal name
name = 'inner_func ' + name
print('inner_func name is : %s' % name)
return inner_func
ret = extern_func('qiguanjie')()
此时程序顺利编译,输出结果为:
exter_func name is : qiguanjie
inner_func name is :
Python装饰器
下面来看我们的主角:装饰器。装饰器是利用了函数也可以作为参数传递和闭包的特性,可以让函数在执行之前或者执行之后方便的添加一些代码,可以将一个普通的方法设置为类方法,提高代码的复用性,是一个非常强大的功能,例如在之前我的博客Flask开发
教程中仅使用了几行代码的装饰器,便完成了用户权限管理,登录状态验证等功能,十分方便且代码复用性极高。
下面我们来看一个小例子,比如我们设计一个装饰器,使得用我们这个装饰器之前先输出一次this is my decorator print out
def my_decorator(func):
print("this is my decorator print out")
return func
@my_decorator
def hello():
print("hello world")
hello()
这里我们装饰器要接受一个参数func
即我们的函数,将函数作为一个参数传给装饰器,我们在输出this is my decorator print out
之后,我们返回我们传输进来的函数进行执行,那么任意一个函数在使用我们这个装饰器之后,在执行之前都会先输出一次this is my decorator print out
,然后再执行函数的功能。
这个功能在这里看着好像没并没有让我们省多少事,仅是让我们少一行print而已,但是在一些高复用的场景,是十分重要的,例如我们网站系统中,我们有些页面只让已登录的用户访问,那么我们只需要写一个验证登录的装饰器,然后在每一个需要登录才能访问的页面的请求函数前添加这个装饰器进行验证即可,这个就非常便捷了。更深入的Python装饰器的使用需要到时候配合我们的具体项目场景进行讲解,等后续我们再展开讲解。