【Python语法】循序渐进理解闭包
作者:互联网
循序渐进理解闭包
1. 闭包初接触
在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。
1.1 闭包基本语法结构:
# 外部函数返回内部函数
def outside(attr1):
# 内部函数使用了外部函数的变量
def inside(attr2):
return attr1 + attr2
# 外部函数返回内部函数
return inside
# 因为outside()返回的是函数,所以a也是一个函数
a = outside(1)
print(a)
# 因此可以对函数传入参数,按照函数的执行顺序,返回
print(a(2))
结果:
<function outside..inside at 0x00000229B7D93828>
3
通过阅读代码注释,可以理解最终的运行结果,但是要深入理解闭包,还需要往下看
1.2 明确变量作用域:
闭包中可能会涉及到内部函数访问外部函数或者全局的变量,复习下Pyhon变量作用域,为学习闭包语法扫清障碍:
- 内部函数无法修改外部函数的值:因为内部函数的变量作用域不在外部
def outter():
x = 1
def inner():
x = 2
print('x in inner: %d' % x)
print('x in outter before call inner: %d' % x)
inner()
print('x in outter after call inner: %d' % x)
o = outter()
结果
x in outter before call inner: 1
x in inner: 2
x in outter after call inner: 1
- 闭包中的访问外部变量:nonlocal或者global
nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。
attr1 = 1
attr2 = 11
def outfunc():
attr1 = 2
attr2 = 22
def inFunc():
nonlocal attr1
global attr2
attr1 += 10
attr2 += 100
print('inner attr1: %d' % attr1)
print('inner attr2: %d' % attr2)
print('outter attr1 before call inFunc: %d' % attr1)
print('outter attr2 before call inFunc: %d' % attr2)
inFunc()
print('outter attr1 after call outFunc: %d' % attr1)
print('outter attr2 after call outFunc: %d' % attr2)
test = outfunc()
结果:
outter attr1 before call inFunc: 2
outter attr2 before call inFunc: 22
inner attr1: 12
inner attr2: 111
outter attr1 after call outFunc: 12
outter attr2 after call outFunc: 22
2. 从for循环开始
2.1 python for循环特性:没有域的概念
先阅读一段代码
flist = []
for i in range(3):
def innerfunc(x):
return x*i
flist.append(innerfunc)
for f in flist:
print(f(2))
让我们猜猜最终的结果,应该是2乘以列表[0,1,2]的每个元素,得到[0,2,4]
运行结果:
4 4 4
加断点分析:
- 第一个for循环的元素结果是一个列表,列表元素都是函数,生成列表后,列表中的参数都不赋值,也不进行计算
flist = [<function innerfunc at 0x0000023575C390D8>,
<function innerfunc at 0x0000023575C39168>,
<function innerfunc at 0x0000023575C2EDC8>]
可以认为是 [x * i, x * i, x * i],此时作用域中i为2, 即列表为[x* 2, x * 2, x * 2]
- 第二个for循环遍历flist,传入参数x的值,此时列表结果即为[2 * 2, 2 * 2, 2 * 2]
2.2 修改代码,让返回的flist具有递增相乘的结果:
flist = []
for i in range(3):
def innerfunc(x):
return x*i
flist.append(innerfunc)
# 在第一次循环的时候,就对flist中的函数赋值,即避免多次遍历flist
print(flist[i](2))
执行结果:
0
2
4
2.3 使用闭包
闭包的外部函数可以保证每次给函数列表flist新增元素时,i值已经固定
flist = []
for i in range(3):
def makerfun(i):
def fun(x):
return x*i
return fun
flist.append(makerfun(i))
for f in flist:
print(f(2))
加断点分析:
- flist = [<function makerfun..fun at 0x000002DB3E22A168>, <function makerfun..fun at 0x000002DB3E21FDC8>, <function makerfun..fun at 0x000002DB3E21FCA8>]
flist依旧是一个函数列表:[makerfun(0), makerfun(1),makerfun(2)]
又因为列表元素是一个闭包,即[x * 0, x * 1, x * 2]
在第一个for循环结束后,i的值已经固定下来了
- 第二个for循环flist,传入参数x的值,此时列表结果即为[2 * 0, 2 * 1, 2 * 2]
执行结果:
0
2
4
体会过上面的例子后,就很好理解闭包的作用:保存当前的运行环境
拿上面的例子来讲,x始终是我们计算时要传入的变量,i看做每次for循环运行的环境标识。
闭包实现了让外部函数保存了每次for循环的环境标识,当我们回头再为flist的每个元素函数传入变量x时,环境标识被闭包固化。
3. 玩棋盘游戏
模拟棋盘游戏,使用闭包,让外部函数实时保存出发的坐标,内部函数完成棋子按照指定方向和步长移动的任务
origin = [0, 0]
def create(pos=origin):
def go(direction, step):
pos[0] += direction[0] * step
pos[1] += direction[1] * step
return pos
return go
player = create()
print(player([1, 0], 10))
print(player([0, 1], 10))
print(player([-1, 0], 10))
看到这段代码是不是对闭包的语法有了更深的理解,注意对照这句话体会:
闭包持有外部函数的变量,这个变量叫做自由变量,当外部函数的声明周期结束后,自由变量依然存在,因为它被闭包引用了,所以不会被回收
4. 闭包特性的另一种实现
闭包这个特性可以用类实现,把类变量看做是自由变量,类的实例持有类变量
但是使用闭包会比使用类占用更少的资源,自由变量占用内存的时间更短
class Animal():
def __init__(self,animal):
self.animal = animal
def sound(self,voice):
print(self.animal, ':', voice)
dog = Animal('dog')
dog.sound('wangwang')
dog.sound('wowo')
def voice(animal):
def sound(voc):
print(animal,':', voc)
return sound
dog = voice('dog')
dog('wangwang')
dog('wowo')
可以看到输出结果是完全一样的,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中
标签:闭包,函数,Python,flist,attr2,循序渐进,print,def 来源: https://blog.csdn.net/weixin_35813749/article/details/110206935