如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(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))
这个用处也是可以用面向对象方便的解决的。相当于生成实例的时候给构造函数传入不同的值。
面向对象里把闭包的这点作用都覆盖了,所以貌似不会也没什么。主要是为了支持函数式编程使用的。