闭包也就是装饰器的实现,其作用在于:
1、不改变原函数内代码段;
2、不改变原函数名称;
3、为原函数增加新的功能,改变输出结果等等。
本文涉及到以下3个知识点:
1、作用域LEGB法则(知道的可以跳过)
2、闭包
3、装饰器
搜索变量名的作用域优先级:L局部 > E嵌套 > G全局 > B内置
L局部(local):
def
构建函数段中,定义的变量
E嵌套(enclosing):当前域该变量名无赋值操作,引用父级;反之引用当前
G全局(global):不在任何函数段内,写在所有函数外边,最外层的变量
B内置(built-in):系统__builtin__
模块中的变量
L和G不用说了,局部和全局,就是函数里和函数外。
B内置,也不提了,一般碰不到。
主要说说E
def fa():
a = 0
def fb():
print(a)
fb()
fa()
fa()
嵌套fb()
,子函数中没有对变量a
进行赋值操作,也是a=xxx
之类的。所以在fb()
的L中没有的时候,可以向上层也就是父级寻找,使用E作用域的中的变量。
假设这里改了一句
def fa():
a = 0
def fb():
print(a)
a = 1 # 这里加一句对a的赋值
fb()
fa()
# 输出结果
Traceback (most recent call last):
File "test.py", line 5, in fb
print(a)
UnboundLocalError: local variable 'a' referenced before assignment
[Finished in 0.2s with exit code 1]
你会发现在print(a)
的时候,就出现了a
未定义的错误。原因在于fb()
的L局部作用域中,有对a进行了赋值这样的操作,a
不能使用父级的定义。而在打印输出时,局部函数中a尚未定义,导致程序报错。
那么局部就不可以修改父级了么?其实是可以的。
原理和global
声明变量,局部变全局是一个道理。
def fa():
a = 0
def fb():
nonlocal a
print(a)
a = 1
fb()
fa()
使用nonlocal
声明后,a
的作用域就扩展到了父级,就又可以使用E嵌套作用域了。
首先以一段闭包实现的代码为例:
def fa():
a = 0
def fb():
nonlocal a
print(a)
a = 1
return fb
f = fa()
f()
f()
# 输出结果
0
1
将闭包函数,之前的嵌套函数对比
发现代码基本一致,只有最后的fb()
变成了return fb
def fa(): # def fa():
a = 0 # a = 0
def fb(): # def fb():
nonlocal a # nonlocal a
print(a) # print(a)
a = 1 # a = 1
return fb # fb()
这就导致程序没有执行内部函数fb
,而是将内部函数打包作为输出。(闭包执行流程可以看当前页最下方的测试记录)
所以通过f = fa()
返回的函数对象f
,其实执行的是,打包好的内部函数fb()
的内容,就是下面这段
def fb():
nonlocal a
print(a)
a = 1
但它的特别之处在于:
f
创建时所携带的父层变量a
会一直存在于内存中,不会因为函数执行完成而消失(除非你主动del f
删掉)
f = fa()
f()
f()
# 输出结果
0
1
其结果分别为0和1的原因在于:
第1次f()
,读取了f
初始化时的a
值进行输出为0
,然后通过a=1
,a
才变成1
第2次f()
,因为a
已经变成1
,所以a
值输出为1
,然后通过a=1
,a
依旧是1
其创建特征在于:函数嵌套,返回函数对象,返回那个函数引用了父层变量。
其使用特征在于:保存局部信息不被销毁。
说完了闭包,来说装饰器。
首先写一个闭包程序,
函数funca
将输入参数+1,作为结果输出。
闭包实现的是任意输入函数,将其输出结果求平方。
所以将funca
打包成new_funca
后,输出结果变成了 ( 1 + 1 ) 2 = 4 (1+1)^2=4 (1+1)2=4
def build_bibao(f):
def bibao(*args):
result = f(*args)
return result**2
return bibao
def funca(num):
return num+1
new_funca = build_bibao(funca)
print(new_funca(1))
# 输出结果
4
那么将上面代码用@
简写,其实就是装饰器的实现。
def build_bibao(f):
def bibao(*args):
result = f(*args)
return result**2
return bibao
@build_bibao
def funca(num):
return num+1
print(funca(1))
# 输出结果
4
装饰器实际上对输入函数funca
做了包装,然后给你返回了包装好的,附加了新功能(结果求平方)的funca
============================================================
下面是自己研究闭包时候,做的一些测试(只做记录,可以忽略)
代码如下:
def funx(x):
return x*x
def build_bibao(func):
def bibao(j):
return func(j)
return bibao
new_funx = build_bibao(funx)
new_funx(5)
添加打印输出,确认其执行过程:
def funx(x):
return x*x
def build_bibao(func):
print(0,type(func),func)
def bibao(j):
print(3,type(j),j)
print(4,type(func.__name__),func.__name__)
print(5,type(func(j)),func(j))
return func(j)
print(1,type(bibao),bibao)
return bibao
new_funx = build_bibao(funx)
print(2,type(new_funx),new_funx)
new_funx(5)
可以依照打印序号,确认执行顺序和结果
0
1 .bibao at 0x000002AA4EDD8730>
2 .bibao at 0x000002AA4EDD8730>
3 5
4 funx
5 25
[Finished in 0.2s]