以python为例讲解闭包机制

以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访问。

总结

函数式编程受到推崇的原因不得不说包含了其中所有数据不可变,解耦了依赖,而闭包这一函数式编程的重要语法结构更是体现了这一优点。

参考

Python Closures: How to use it and Why? - Programiz

原文地址:https://www.cnblogs.com/lynsyklate/p/7869381.html