• 以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

  • 相关阅读:
    [debug]重定义默认參数
    UVA 1329 Corporative Network【并查集】
    fork同一时候创建多个子进程的方法
    CSS3弹性布局内容对齐(justify-content)属性使用具体解释
    python监控linux性能以及进程消耗的性能
    Android实战简易教程-第十三枪(五大布局研究)
    Linux 截图
    (hdu step 7.1.6)最大三角形(凸包的应用——在n个点中找到3个点,它们所形成的三角形面积最大)
    【SPOJ-GSHOP】Rama and Friends【贪心】【细节】
    【编程题目】在字符串中找出连续最长的数字串,并把这个串的长度返回
  • 原文地址:https://www.cnblogs.com/lynsyklate/p/7869381.html
Copyright © 2020-2023  润新知