• Python中的闭包


    今天我们来学习下Python中的闭包。

    什么是闭包

    当我们在外部函数中定义了一个内部函数,并且内部函数能够读取到外部函数内的变量,这种函数我们就称为闭包。简单来说,闭包就是能够读取外部函数内的变量的函数。闭包的架子大概是这样:

    def demo_outer(x):
        def demo_inner(y):
            print("x的值:{}, y的值:{}, x + y 的值:{}".format(x, y, x + y))
        return demo_inner
    
    
    do = demo_outer(12)
    do(34)
    

    上面代码执行结果如下:

    x的值:12, y的值:34, x + y 的值:46
    

    上面的闭包代码,和我们之前学习的装饰器类似,我们在外部函数 demo_outer 下定义了一个内部函数 demo_inner ,并且外部函数的返回值就是内部函数,同时在内部函数中,我们引用到了外部函数的变量 x ,而闭包的作用就是可以将外层函数的变量保存在内存中而不被销毁。

    闭包的实例

    我们先准备一个函数add(),每次调用该函数都只能传一个数 num ,每次返回的结果都是基于上一次结果的值 sum 进行累加操作。例如,sum的默认值为0,如果我们依次调用 add(10)、add(20)、add(30) 后,期望得到的最终结果是 sum = 60。

    对于该问题,因为需要在函数内部对函数外部的变量进行处理,我们可能会考虑使用 global 来处理。

    sum = 0
    
    
    def get_add_sum(num):
        global sum
        sum += num
        return sum
    
    
    print(get_add_sum(10))  # 输出:10
    print(get_add_sum(20))  # 输出:30
    print(get_add_sum(30))  # 输出:60
    
    print(sum)  # 输出:60
    

    上面代码中,我们在函数中通过全局变量 global 将 sum 声明为全局变量,最终返回的结果也符合我们的期望。但因为全局变量太灵活了,不同模块函数都能自由访问到全局变量,所以一般不推荐在函数内部中定义全局变量。

    对于上面的问题,除了使用全局变量外,我们还可以通过 闭包 来实现。

    sum = 0
    
    
    def get_add_sum(sum):
        def add_num(num):
            nonlocal sum
            sum += num
            return sum
        return add_num
    
    
    add = get_add_sum(sum)
    print(add(10))  # 输出:10
    print(add(20))  # 输出:30
    print(add(30))  # 输出:60
    
    print(sum)  # 输出:0
    

    在上面的闭包函数中,定义了外层函数和内层嵌套函数,执行过程中,调用外层函数 get_add_sum 返回的就是内层嵌套函数 add_num ,因为Python中函数也是对象,所以可以直接返回 add_num 。

    而在内层嵌套函数中则实现了我们想要的累加操作,在这里我们需使用关键字 nonlocal 来声明外层函数中的变量,否则无法对外层函数中的变量进行修改,其作用域只在闭包函数里面,所以当我们在最后打印 sum 最终结果,输出的仍然是其初始值 0 。

    我们再来看一个例子:

    def outer():
        res = []
        for i in range(3):
            print("外部的i值:{}".format(i))
            def inner(x):
                print("内部的i值:{}".format(i))
                return i + x
            res.append(inner)
        return res
    
    
    temp = outer()
    res = [i(10) for i in temp]
    print(res)
    

    上面代码的执行结果如下:

    外部的i值:0
    外部的i值:1
    外部的i值:2
    内部的i值:2
    内部的i值:2
    内部的i值:2
    [12, 12, 12]
    

    可以看到 res 的结果并不是 [10, 11, 12] ,因为 i 是外层函数的变量,并且其值是按 0,1,2 变化,因为该函数中无法保存外层函数的变量,故对于内部函数,其实际只能访问到最后外部变量的值 2 ,导致最终结果为:[12, 12, 12]。

    在这里,我们可以使用闭包来保存函数的外部变量,修改代码如下:

    def outer(i):
        print("外部的i值:{}".format(i))
        def inner(x):
            print("内部的i值:{}".format(i))
            return i + x
        return inner
    
    
    def demo():
        res = []
        for i in range(3):
            res.append(outer(i))
        return res
    
    
    temp = demo()
    res = [i(10) for i in temp]
    print(res)
    

    上面代码的执行结果如下:

    外部的i值:0
    外部的i值:1
    外部的i值:2
    内部的i值:0
    内部的i值:1
    内部的i值:2
    [10, 11, 12]
    

    闭包和装饰器的区别

    在Python中,闭包传递的参数是变量,装饰器传递的参数是函数对象,它们只是在传参内容上有不同。那么装饰器是不是属于闭包的一种呢,我们要怎么判断一个函数是否是闭包呢?

    我们可以打印 闭包 和 装饰器 的属性 __closure__ ,如果一个函数是闭包,那么查看该属性将会返回一个cell对象组成的tuple对象。

    def demo_outer(x):
        """
        闭包
        """
        def demo_inner(y):
            print("x的值:{}, y的值:{}, x + y 的值:{}".format(x, y, x + y))
        return demo_inner
    
    
    def demo_decorator(func):
        """
        装饰器
        """
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
    
    @demo_decorator
    def method():
        pass
    
    
    do = demo_outer(5)  # 闭包
    print("闭包的属性:{}".format(do.__closure__))
    
    dd = demo_decorator(method)  # 装饰器
    print("装饰器的属性:{}".format(dd.__closure__))
    

    执行结果如下:

    闭包的属性:(<cell at 0x0000023F48C3C8E8: int object at 0x00007FF92017D4A0>,)
    装饰器的属性:(<cell at 0x0000023F48C3C8B8: function object at 0x0000023F48CB97B8>,)
    

    所以结合上面的结果,我们也可以这样理解:装饰器本质上就是一个闭包函数,它只是一个传递函数对象的闭包函数。

  • 相关阅读:
    全链路压测(4):全链路压测的价值是什么?
    基于SVN的版本范围汇总
    一篇值得思考的职业教育之路!
    分享35个讨人喜欢的漂亮进度条UI设计
    转一篇难得的好文章CPU流水线的探秘之旅
    超棒的获奖动物摄影作品集
    解决web.py在SAE云中的Session使用问题
    2012年度最新免费web开发设计资源荟萃
    Endless icon: 每天都更新的图标集
    不容错过的超棒Javascript日期处理类库Moment.js
  • 原文地址:https://www.cnblogs.com/wintest/p/16275667.html
Copyright © 2020-2023  润新知