python闭包问题:UnboundLocalError: local variable 'x' referenced before assignment

一. 闭包问题

  1. 闭包中的值保存在返回函数的cell object中。

     def fun_a(msg):
         def fun_b():
             print("fun_b namespace msg is ", msg)
         return fun_b
     
     
     func =fun_a("hello")
     print(func.__closure__[0].cell_contents)
    
     >>> hello
    
  2. func.__closure__返回cell object组成的元组,每个cell object都是只读的,不可改变。

     func.__closure__[0].cell_contents = "world"
     >>> AttributeError: attribute 'cell_contents' of 'cell' objects is not writable
    
  3. 子函数改变父函数中的值:

    1. 如果在函数里内函数里面直接msg赋值时可以的,因为此时的msg是func_b的locals变量,和func_a中的msg没有关系,也改变不了func_a中的变量

       def fun_a(msg):
           def fun_b():
               msg = " world"
               print("fun_b namespace msg is ", msg)
           yield fun_b
           return msg
      
    2. 在func_b中没有定义新的msg时,直接使用,但接下来改变msg的值,会报错:UnboundLocalError: local variable ‘msg’ referenced before assignment

       def fun_a(msg):
           def fun_b():
               a = msg
               msg = " world"
               print("fun_b namespace msg is ", msg)
           yield fun_b
           return msg
      
    3. 只有在func_b使用外面的函数变量时,定义nonlocal,才可以改变外面函数的值,而且这个值是一个引用,更改后即使外面的函数,这个值也修改了。

       def fun_a(msg):
           def fun_b():
               nonlocal msg
               msg += " world"
               print("fun_b namespace msg is ", msg)
           yield fun_b
           return msg
       
       
       def main():
           gen = fun_a("hello")
           msg = yield from gen
           print("fun_a namespace msg is :", msg)
       
       
       for func in main():
           func()
      

      结果

       fun_b namespace msg is  hello world
       fun_a namespace msg is : hello world
      
    4. 以上的原因:python local 变量定义规则:

      1. 当对一个作用域中的变量进行赋值时,该变量将成为该作用域的局部变量,并在外部作用域中隐藏任何类似命名的变量,所以外部变量只可以读,但是不可以赋值,赋值的话,就将变量当做本地变量,屏蔽了外部作用域的变量了,如果没有定义的地方,会报错。

      2. 所以在一个作用域中要改变全局变量,必须用global声明变量时全局作用域的,而闭包函数需要nonlocals

      3. 使用外部变量导致的问题:

         squares = []
         for x in range(5):
             squares.append(lambda: x**2)
        
         >>> squares[2]()
         16
         >>> squares[4]()
         16
        
         
         >>> x = 8
         >>> squares[2]()
         64
        
        1. x不是lambda函数的局部变量,而是函数外边的变量,x这个变量一直存在,lambda执行时才回去读x的值,如果改变x的值,lambda的结果还会变

           >>> x = 8
           >>> squares[2]()
           64
          
        2. 函数的默认参数都是在定义时就已经初始化好的,所以会导致默认参数是列表的话,函数中会出现错误,而如果函数的参数是外部变量的话,只有当函数调用时,才会确定参数的值。

        3. 避免这个问题,只需要将外部变量变为本地变量,将外部变量赋值给一个本地变量。

           >>> squares = []
           >>> for x in range(5):
           ...     squares.append(lambda n=x: n**2)
          

二. 将一个变量保存到一个函数中

  1. 闭包

     fn = (lambda x: lambda: x)(value)
    

    这样,fn所代表的的lambda 函数就绑定了value

  2. 偏函数

     from functools import partial
     a = lambda x: x
     b = partial(a, 234)
     print(b, b(), b.args, b.func, b.keywords)
    
     结果:
     functools.partial(<function <lambda> at 0x028FC6A8>, 234) 234 (234,) <function <lambda> at 0x028FC6A8> {}
    

    使用偏函数可以达到同样的目的,只不过偏函数属于另外的类型,不再是简单的函数,偏函数内部保存了参数,不会出现闭包的问题。

三. 变量作用域:

  1. 如果希望更改全局变量,或者通过改变全局变量来改变函数中的变量使用global
  2. 在闭包里面,nonlocal 指定为非本地变量,注意他也不是全局变量

链接:
参考链接

Python 本地变量错误

猜你喜欢

转载自blog.csdn.net/vwangyanwei/article/details/82810192