Python闭包

  

闭包

  
  

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

     

这里是一个闭包的例子:

  
 <代码类="语言python "> def addx (x):
  def加法器(y):
  返回x + y
  返回加法器
  
  if __name__==癬_main__”:
  func=addx (10)
  打印(func (1))
  打印(func (2))
  打印(func(3)  
  

执行结果:

  
 <代码> 11
  12
  13  
  

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

  

闭包的注意事项

  

作用域的问题

  

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

  
 <代码类="语言python "> def foo ():
  x=0
  def f ():
  x=1
  返回x
  打印(x) # 0
  打印(f ()) # 1
  打印(x) # 0
  
  if __name__==癬_main__”:
  foo()  
  

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

  

操作外部变量

  

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

  
 <代码类="语言python "> def广场():
  x=0
  
  def f ():
  x=x + 1
  返回x *
  返回f  
  

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

  
 <代码> UnboundLocalError:局部变量“x”引用之前赋值 
  

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

  
 <代码类="语言python "> def广场():
  x=[0]
  
  def f ():
  x [0]=x [0] + 1
  返回x [0] * [0]
  返回f  
  

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

  

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

  
 <代码类="语言python "> def广场():
  x=0
  
  def f ():
  外地x
  x=x + 1
  返回x *
  返回f
  
  if __name__==癬_main__”:
  func=广场()
  print (func ())
  print (func ())
  print (func())  
  

闭包的作用

  

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

  

让函数还可以拥有状态

  

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

  
 <代码类="语言python "> def create_piece ():
  x=0
  y=0
  
  def移动(offset_x=0, offset_y=0):
  外地x, y
  x +=offset_x
  y +=offset_y
  返回x, y
  返回移动
  
  if __name__==癬_main__”:
  球员=create_piece() #这里是不是和面向对象里的使用前,生成对象的实例很像吗?
  打印(球员())#打印当前坐标
  球员(1,1)#移动棋子
  打印(球员())#打印当前坐标
  打印(球员(1、3)#再移动棋子并打印坐标

Python闭包