• 10分钟让你熟练Python闭包


    初识闭包

    什么是闭包

    闭包的本质是函数

    如何创建闭包

    1. 嵌套函数定义(外部函数,内部函数)
    2. 内部函数使用外部函数中定义的变量
    3. 外部函数一定要有返回值,返回内部函数名

    【实例】定义一个闭包:

    def Out(a):
    	def In(b):
            return a+b
        return In
    
    • 1
    • 2
    • 3
    • 4

    如何使用闭包

    【实例】使用闭包计算两个数字的和:

    # 计算两个数字的和
    def funcOut(num1):
        def funcIn(num2):
            return num2+num1
        return funcIn # 返回内部函数
    
    a = 10
    b = 20
    funcIn = funcOut(a) # 此时变量funcIn是函数类型,可以调用
    result = funcIn(b) # 调用的是funcOut()的内部函数funcIn()
    print(result)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行结果:

    30

    【注意】在上述例子中,执行funcOut(a)以及funcIn(b)后,funcOut(a)里面的内容并不会因为funcIn(b)执行完而被释放。当在再次执行funcIn (100) 时结果为110。在闭包中,可以使用外部函数定义的变量,当直接改变外部函数变量的时候,内部函数会认为这是一个新定义的变量,从而导致报错。如果需要改变外部函数变量,需要使用nonlocal进行声明。

    【实例】修改外部函数变量值

    def funcOut(num1):
        def funcIn(num2):
            nonlocal num1 # 只有声明了外部函数变量再修改才是合法的
            num += 1
            return num2+num1
        return funcIn
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    闭包应用

    【实例】求(10, 10), (20, 20)距离原点(0, 0)的距离

    # 使用普通函数实现
    from math import sqrt
    
    def getDis(x1, y1, x2, y2):
        return sqrt((x1-x2)**2 + (y1-y2)**2)
    
    # 使用闭包实现
    def getDisOut(x1, y1):
        def getDisIn(x2, y2):
            return sqrt((x1-x2)**2 + (y1-y2)**2)
        return getDisIn
    
    # 普通函数
    dis = getDis(0, 0, 10, 10)
    print("(10, 10)距离远点的距离:{}".format(dis))
    dis = getDis(0, 0, 20, 20)
    print("(20, 20)距离远点的距离:{}".format(dis))
    
    # 闭包
    disIn = getDisOut(0, 0)
    dis = disIn(10, 10)
    print("(10, 10)距离远点的距离:{}".format(dis))
    dis = disIn(20, 20)
    print("(20, 20)距离远点的距离:{}".format(dis))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在使用闭包后,由于闭包特殊的性质,即调用Out函数和In函数后,原先Out函数变量里面的值并不会被释放,在处理上述类型问题的时候可以减少代码量。

    闭包的特殊用途

    可以在不修改现有功能源码的前提下,增加新的功能。

    比如:日志功能(统计访问时间,访问功能,写道日志文件中),权限验证(下载之前,验证是否为会员)

    【实例】不用闭包的情况下添加日志功能

    # 实现日志功能
    import time
    
    
    def write_log(func):
        try:
            file = open('log.txt', 'a', encoding='utf-8')
            # 写入相关信息(访问的函数名称,访问的时间)
            file.write(func.__name__)
            file.write('\t')
            # 写入访问时间
            file.write(time.asctime())
            file.write('\n')
    
        except Exception as e:
            print(e.args)
        finally:
            # 关闭文件
            file.close()
    
    
    def func1():
        write_log(func1)
        print('我是功能1')
    
    
    def func2():
        write_log(func2)
        print('我是功能2')
    
    
    if __name__ == "__main__":
        func1()
        func2()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    运行结果:

    func1 Mon Oct 31 21:51:34 2022
    func2 Mon Oct 31 21:51:34 2022

    当不使用闭包添加功能的时候,大概率要在源代码上面进行修改,这就违反了程序开发的开闭原则:对添加功能开放,对修改源代码关闭。当代码量少的时候还好,一旦添加的代码涉及修改元逻辑就有可能导致新的问题出现。

    【实例】使用闭包添加日志功能

    # 闭包的应用:添加日志功能
    import time
    
    
    def write_log(func):
        try:
            file = open('log.txt', 'a', encoding='utf-8')
            # 写入相关信息(访问的函数名称,访问的时间)
            file.write(func.__name__)
            file.write('\t')
            # 写入访问时间
            file.write(time.asctime())
            file.write('\n')
    
        except Exception as e:
            print(e.args)
        finally:
            # 关闭文件
            file.close()
    
    
    def func_out(func):
        def func_in():
            write_log(func)
            func()
        return func_in
    
    
    def func1():
        print('我是功能1')
    
    
    def func2():
        print('我是功能2')
    
    
    if __name__ == "__main__":
        # 闭包的调用
        func1 = func_out(func1) 
        func2 = func_out(func2)
        func1()
        func2()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    这里使用了闭包给原函数添加功能,首先当执行func1 = func_out(func1)时,func1被赋值为闭包外部函数变量值为func1()的内部函数func_in(), 所以当后面执行func1()的时候,其实执行的是闭包里面func_in()的代码。由于传实参的时候,func1()被当成对象传递进闭包中,按照func_in()执行步骤,先执行了以func1()为实参的write_log(func1)再执行从闭包外部函数传递进来的func()(此时func()就等于func1())。通过这样的方式实现了不修改原函数源代码,为原函数添加了日志功能。

    这里可能有点绕,但是闭包的逻辑本身就是很绕,希望小伙伴们自己多写一下这里的代码进行体会。

    闭包在实际开发中用处非常广泛,希望同学们好好体会。

  • 相关阅读:
    19、商品微服务-srv层实现
    大数据-Hadoop-基础篇-第九章-Storm
    STM32 从0开始移植FreeRTOS
    Unity UI不被3D物体遮挡
    CCF CSP认证 历年题目自练Day37
    基于IDEA集成环境---Nacos安装
    ArcGIS Map Sdk for unity使用
    蓝蓝设计提供大屏信息软件UI设计服务
    C++初学者指南第一步---2. Hello world
    ROS2与turtlebot4仿真入门教程-安装ROS2
  • 原文地址:https://blog.csdn.net/nine_mink/article/details/127624729