闭包的定义,及基本概念;实例;闭包定义中的典型错误分析及解决办法;闭包在实践的应用。
闭包 创建时间:2018/8/11 修改时间:2018/11/18
1.定义:
闭包:是由函数及其相关的引用环境组合而成实体(即:闭包=函数+引用环境)
内部函数引用外部作用域(非全局)变量,会把引用环境和函数体打包成一个整体(闭包)返回,内部函数被认为是闭包(closure).
函数:
可作另一个函数参数或返回值,赋给变量。函数可嵌套定义;
每次调用ExFunc函数返回新闭包实例,实例之间是隔离;
一个函数可返回一个计算结果或函数。返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量
引用环境
指在程序执行中的某个点所有处于活跃状态的约束(变量名字和其所代表的对象之间的联系)所组成的集合。
2.实例:
def ExFunc(n): # 函数返回的就是闭包
sum = n
def InsFunc(): # 内嵌函数是闭包
return sum + 1 # 引用到外层函数中的局部变量sum
return InsFunc # 返回值是函数
myFunc = ExFunc(10) # 返回新闭包实例
a1 = myFunc() # 11
myAnotherFunc = ExFunc(20) # 返回新闭包实例
a2 = myAnotherFunc() # 21
print(a1, a2)
实例2
def add(x):
def adder(y):
return x + y
return adder
a = add(8)
b=add
type(a) #<type 'function'>
a.__name__ #'adder'
a(10) #18
b(8)(10) #18
2.使用闭包注意事项:
2.1.闭包中不能修改外部作用域局部变量
实例1:-每次调用闭包函数时都对变量a进行递增的操作
#使用闭包时经典错误代码
def foo():
a = 1
def bar():
# nonlocal a
a = a + 1 #修改外部作用域局部变量
#python规则指定所有在赋值语句左面的变量都是局部变量
return a
return bar
c = foo()
print(c()) #错误
#解决方法
def foo():
a = [1]
def bar():
a[0] = a[0] + 1
return a[0]
return bar
2.2. 循环结束循环体中临时变量i不会销毁存在于执行环境中: 注意:
1)返回闭包时:返回函数不要引用任何循环变量,或后续会发生变化变量。 2)如要引用循环变量 方法1:该函数创建一个参数,用这个参数绑定到循环变量 ;如实例 1 方法2:迭代 方法;参见实例 2 再创建一个函数,用该函数的参数绑定循环变量当前的值, 无论该循环变量后续如何更改,已绑定到函数参数的值不变
实例2:
#python的函数只有在执行时,才会去找函数体里的变量的值。
lst = []
for i in range(3):
def foo(x):
print (x + i)
lst.append(foo)
for f in lst:
f(2) #4,4,4
#解决方法
for i in range(3):
def foo(x,y=i):
print( x + y)
lst.append(foo)
实例3 :
def count():
lst = []
for i in range(1, 4):
def f():
return i*i
lst.append(f)
return lst
f1, f2, f3 = count()
f1(),f2(),f3() #9,9,9
#解决办法
def count():
def f(j):
def g():
return j*j
return g
lst = []
for i in range(1, 4):
lst.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return lst
# 结果:
f1, f2, f3 = count()
f1(),f2(),f3() #1,4,9
3.作用
闭包主要是在函数式开发过程中使用。
用途1
当闭包执行完后,仍然能够保持住当前的运行环境。
希望函数每次执行结果基于这个函数上次的运行结果。
例棋盘游戏:
棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),
步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原
来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction,step):
# 先判断参数direction,step合法性,direction不能斜走step不能为负
# 对新生成的x,y坐标合法性判断处理,
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y #注意!此处不能写成 pos = [new_x, new_y]
return pos
return player
player = create() # 创建棋子player,起点为原点
print player([1,0],10) # 向x轴正方向移动10步
print player([0,1],20) # 向y轴正方向移动20步
print player([-1,0],10) # 向x轴负方向移动10步
输出:
[10, 0]
[10, 20]
[0, 20]
用途2:
闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,
我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。
比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter
# 取得文件"result.txt"中含有"pass"关键字的行
filter = make_filter("pass")
filter_result = filter("result.txt")