本篇文章将介绍Python中的“闭包(Closure)”,进一步理解作用域(scope)和自由变量的概念。
什么是闭包closure
闭包,从字面的意思翻译就是一个“封闭的包裹”,在包裹外的人,无法拿到包裹里的东西,如果你在包裹里,就能尽情使用包裹内的东西。闭包可以保存在函数作用范围内的状态,不会受到其他函数的影响,且无法从其他函数获取闭包内的数据,也可以避免建立许多全局变量相互干扰。
闭包的定义:
一个函数中定义了另一个函数。
被定义的函数使用了原函数的变量。
原函数返回了被定义的函数。
下方的代码,就是一个简单的闭包示例。
def a(msg):
i = '!!!' # 闭包开始
def b(): # A函数内定义了B函数
print(msg + i) # B函数使用了A函数的变量
return b # 将B函数作为返回值,闭包结束
s = a('hello')
s() # 输出 hello
什么是作用域scope
作用域Scope指的是变量、常量、函数或其他定义语句可以“被访问到”的范围。Python共定义了四种作用域,由内而外分别是局部(Local)、闭包外函数(Enclosing)、全局(Global)和内置(Builtin)。内部的作用域无法影响到外部作用域。
闭包的示例应用
下方的代码,会建立一个avg
函数的闭包,执行后虽然test()
执行了三次,但因为每次执行时保留了一个作用域的绑定关系,所以会不断将传入的数值进行计算,最后就会得到11的结果。
def count(): # 建立一个count函数
a = [] # 函数内有局部变量a是列表
def avg(val): # 建立内嵌函数avg(闭包)
a.append(val) # 将参数数值加入变量a
print(a) # 打印a
return sum(a)/len(a) # 返回a列表所有数值的平均值
return avg # 返回avg
test = count()
test(10) # 将10存入a
test(11) # 将11存入a
test(12) # 打印11
自由变量nonlocal
不过如果将上方的例子,改成变量的做法,可能会发生错误,因为在cal
函数里的变量a后方使用了“等号”,意义等同于变量的“赋值”,换句话说是新建了一个局部变量a,就造成了命名空间里名称重叠的问题。
def count():
a = 1 # 新增变量a等于1
def cal(val): # 建立内嵌函数cal(闭包)
a = a + val # 设定变量a等于a加val
return a # 返回a
return cal # 返回cal
test = count()
test(10)
test(11)
如果必须这么做,可以使用nonlocal
的方式,声明这个变量是“自由变量”(不是这个区域内的变量),就能正常使用这个变量。
def count():
a = 1
def cal(val):
nonlocal a # 声明a为自由变量
a = a + val
return a
return cal
test = count()
test(10)
test(11)
test(12) # 输出34(1 + 10 + 11 + 12)