以python为例讲解闭包机制
缘起
在学习JS的过程中,总是无可避免的接触到闭包机制,尤其是接触到react后,其函数式的编程思想更是将闭包发扬光大,作为函数式编程的重要语法结构,python自然也拥有闭包这一语法结构。
在这篇文章中我会介绍如何产生一个闭包函数,闭包函数产生的原因和使用它的优点。
回顾python的函数作用域
闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了*变量的函数。这个被引用的*变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。
我们从最简单的嵌套函数来了解闭包
def transmit_to_space(message):
def data_transmitter():
"这是一个嵌套函数"
print(message)
data_transmitter()
print(transmit_to_space("Test message"))
我们可以发现,嵌套在外层函数transmit_to_space里的data_transmitter访问了外层函数提供的形参message。我们可以得知,局部作用域能够访问外部作用域。
def print_msg(number):
def printer():
"Here we are using the nonlocal keyword"
nonlocal number
number=3
print(number)
printer()
print(number)
print_msg(9)
在上面的代码中我们使用了nonlocal这个关键字,正如字面意思,它使变量变为非局部的。我们知道局部作用域可以访问外部作用域,但不能更改他,一旦进行了更改就会把其当成局部变量,如果在修改前进行了读取则会报错。
通常为了解决这个问题我们会使用global直接从全局进行修改,这显然很危险。因此python3引入了nonlocal这个关键字使解释器能够从外部作用域查找变量名。
假如我们不使用nonlocal则会打印3,9,在使用nonlocal后则会打印3,3。使用nonlocal是的局部嵌套函数对于外层函数的变量进行了修改。
一切皆对象
上面的例子除了介绍了nonlocal,无非老调重弹,介绍了作用域的问题,这就是闭包吗?
我们知道python中一切皆对象,函数也是对象的一种,因此就有了一个相当有趣的例子
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
func()
这真的是一个很suprise的事情,内部函数作为对象已经脱离了其作用域,但是我们在执行是竟然发现它打印出我们传进去的形参。
其实原因很简单:本质上是闭包使得变量的值始终保存在内存中。本来,在执行完transmit_to_space后message这个变量应该被销毁,但由于data_transmitter使用了闭包,且在调用完仍然存在,message的销毁被延后了,仍然存储在内存中。
像这种,transmit_to_space执行完毕message却保存下来的过程我们称之为闭包。
在python中,函数本身也是对象,在我们调用transmit_to_space("hello"),我们传进去的参数作为局部变量储存了下来。在这个对象存在的生命周期我们都能访问它其中的变量。
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
func2 = transmit_to_space("toto")
func()
func2()
"""
hello
toto
"""
从上面的例子可以看出message的存储与transmit_to_space是没有关系的
我们可以看下面这个例子加深理解
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
del transmit_to_space
func()
正因为我们在调用该函数时,创建了该函数对象的一个实例,因此即使我们删除原先的函数,func仍能执行。
我们知道函数调用过程中局域变量是栈的压入和推出所实现的,那么在执行func的时候,message存储在哪?这里我们得提到一个魔法属性__closure__
def transmit_to_space(message):
def data_transmitter():
print(message)
return data_transmitter
func = transmit_to_space("hello")
print(func.__closure__[0].cell_contents)
闭包函数相比普通函数会多出一个__closure__的属性,闭包的cells中引用了函数执行过程中来自外部作用域却需要使用的变量。
具体可以参考这篇文章
闭包闭包
从上面的代码中我们可以总结出来创建闭包的条件:
即闭包函数中存在执行完毕仍然存在的对象
闭包使变量隐藏起来,我们很难从外部改变它的状态(其实可以通过__closure__访问),这使得人们很容易追踪变量的改变。
当多个模块依赖一个共有数据时,且该共有数据可以被修改,那么一个数据的改变可能引起多个模块不可预料的结果,使用闭包来对外隐藏这些数据,通过固定的方法进行修改能够极大减少bug的发生,redux的设计就是一个很好的例子。
我们可以使用python实现一个简单的redux
createStore(state, stateChanger):
getState = lambda:state
dispatch = lambda action:stateChanger(state,action)
return {
"getState":getState,
"dispatch":dispatch
}
appState = {
"status":"good"
}
def stateChanger(state,action):
if action == "good":
state["status"] = "good"
elif action == "bad":
state["status"]="bad"
action = "bad"
store = createStore(appState,stateChanger)
print(store["getState"]())
store["dispatch"](action)
print(store["getState"]())
使用createstore我们创建了一个store对象,只能通过固定的stateChanger访问。
总结
函数式编程受到推崇的原因不得不说包含了其中所有数据不可变,解耦了依赖,而闭包这一函数式编程的重要语法结构更是体现了这一优点。