Python闭包

闭包

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

创新互联是一家专注于网站建设、成都网站设计与策划设计,赣县网站建设哪家好?创新互联做网站,专注于网站建设十多年,网设计领域的专业建站公司;建站业务涵盖:赣县等地区。赣县做网站价格咨询:13518219792

这里是一个闭包的例子:

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))

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

总结

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


名称栏目:Python闭包
分享URL:http://scyanting.com/article/jggeih.html