Python 闭包
admin
2023-07-15 06:42:07
0次

闭包

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)

这里是一个闭包的例子:

def addx(x):
    def adder(y):
        return x + y
    return adder

if __name__ == '__main__':
    func = addx(10)
    print(func(1))
    print(func(2))
    print(func(3))

执行结果:

11
12
13

这里例子里,adder(y) 就是一个内部函数。它里面引用了外部的变量x,x是外部作用域addx(x)里的变量,但是不是全局变量。所以内部的adder(y)是一个闭包。
精炼一些:闭包=函数块+定义函数时的环境。adder就是函数块,x就是环境。

闭包的注意事项

作用域的问题

这个例子里应该是一个函数的作用域的问题,和闭包没太大关系:

def foo():
    x = 0
    def f():
        x = 1
        return x
    print(x)  # 0
    print(f())  # 1
    print(x)  # 0

if __name__ == '__main__':
    foo()

内部函数和外部函数都定义了变量x。这里内部没有引用外部的变量x,还是生成了一个自己的局部变量,也叫x,这个变量还外部的函数的x变量是没有关系的。所以这里的问题只是一个作用域的问题。

操作外部变量

下面的这个函数是有问题的,语法有错误:

def squares():
    x = 0

    def f():
        x = x + 1
        return x * x
    return f

看似符合闭包的要求,但是标量x出现在了赋值符号 '=' 的左边。python规则指定所有在赋值语句左面的变量都是局部变量。这里因为x被认为是局部变量,然后再执行x+1的时候就只会在局部里找这个x的值,但是找不到,所以就报错了,错误信息如下:

UnboundLocalError: local variable 'x' referenced before assignment

解决方案1
避免直接引用外部,如果引用的是外部的列表、字典等。那么变量名就不会直接出现在赋值符号左边了:

def squares():
    x = [0]

    def f():
        x[0] = x[0] + 1
        return x[0] * x[0]
    return f

如果直接要引用的是外部的列表、字典这类变量,用就不会遇到这类问题。但是这里例子里,这么做感觉也不好

解决方案2
如果要引用的外部变量就是一个简单的数值或者字符串,虽然上面的方法可行,但是还有更好的做法。
使用 nonlocal 声明,把内层的局部变量设置成外层局部可用,但是还不是全局的。类似声明全局变量的 global 的用法。这里主要是因为python里不需要像其他语言里,有类型的var之类的关键字来声明变量。变量直接赋值就完成了声明,平时用起来很方便,但是在这里,因为在操作的时候变量直接出现在赋值符号左边了,就会被认为新定义了一个局部变量了。
完整的示例:

def squares():
    x = 0

    def f():
        nonlocal x
        x = x + 1
        return x * x
    return f

if __name__ == '__main__':
    func = squares()
    print(func())
    print(func())
    print(func())

闭包的作用

闭包主要是在函数式开发过程中使用。下面介绍的两种使用场景,用面向对象也是可以很简单的实现的。但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。

让函数还可以拥有状态

当闭包执行完后,仍然能够保持住当前的运行环境。上面也提过了:闭包=函数块+定义函数时的环境。这个环境可以在闭包里改变,并且保持下去。
下面是一个类似移动棋子的例子。先在坐标0,0创建棋子。然后可以用内部函数移动棋子。移动后,棋子的状态也更新了,下次再移动棋子,就是在之前的位置的基础上再进行的移动:

def create_piece():
    x = 0
    y = 0

    def move(offset_x=0, offset_y=0):
        nonlocal x, y
        x += offset_x
        y += offset_y
        return x, y
    return move

if __name__ == '__main__':
    player = create_piece()  # 这里是不是和面向对象里的使用前,生成对象的实例很像?
    print(player())  # 打印当前坐标
    player(1, 1)  # 移动棋子
    print(player())  # 打印当前坐标
    print(player(1, 3))  # 再移动棋子并打印坐标

配置函数的参数

闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。
下面的例子,统计字符串里,某个特定的字符出现了多少次。要统计哪个字符,就生成一个对应的闭包,生成的时候把参数传入:

def count_letter(a):
    def count(s):
        x = 0
        for i in s:
            if i == a:
                x += 1
        return x
    return count

if __name__ == '__main__':
    s = 'Hello World !!!'
    count_l = count_letter('l')
    count_space = count_letter(' ')
    print(count_l(s))
    print(count_space(s))

这个用处也是可以用面向对象方便的解决的。相当于生成实例的时候给构造函数传入不同的值。

总结

面向对象里把闭包的这点作用都覆盖了,所以貌似不会也没什么。主要是为了支持函数式编程使用的。

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...