1. 程式人生 > >python閉包問題

python閉包問題

一. 閉包問題

  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> {}
    

    使用偏函式可以達到同樣的目的,只不過偏函式屬於另外的型別,不再是簡單的函式,偏函式內部儲存了引數,不會出現閉包的問題。

連結:

參考連結