1. 程式人生 > >python 學習彙總53:閉包(中級學習- tcy)

python 學習彙總53:閉包(中級學習- tcy)

閉包的定義,及基本概念;例項;閉包定義中的典型錯誤分析及解決辦法;閉包在實踐的應用。
 

閉包                 建立時間:2018/8/11  修改時間:2018/11/18


 1.定義: 

閉包:是由函式及其相關的引用環境組合而成實體(即:閉包=函式+引用環境)
內部函式引用外部作用域(非全域性)變數,會把引用環境和函式體打包成一個整體(閉包)返回,內部函式被認為是閉包(closure).
函式:
可作另一個函式引數或返回值,賦給變數。函式可巢狀定義;
每次呼叫ExFunc函式返回新閉包例項,例項之間是隔離;

一個函式可返回一個計算結果或函式。返回一個函式時,牢記該函式並未執行,返回函式中不要引用任何可能會變化的變數
引用環境
指在程式執行中的某個點所有處於活躍狀態的約束(變數名字和其所代表的物件之間的聯絡)所組成的集合。  

2.例項:

def ExFunc(n): # 函式返回的就是閉包
   sum = n

   def InsFunc(): # 內嵌函式是閉包
     return sum + 1 # 引用到外層函式中的區域性變數sum

   return InsFunc # 返回值是函式

myFunc = ExFunc(10) # 返回新閉包例項
a1 = myFunc()    # 11
myAnotherFunc = ExFunc(20) # 返回新閉包例項
a2 = myAnotherFunc()    # 21
print(a1, a2)

例項2
def add(x):
  def adder(y):
    return x + y
  return adder

a = add(8)
b=add
type(a)    #<type 'function'>
a.__name__ #'adder'
a(10)    #18
b(8)(10) #18
2.使用閉包注意事項: 
2.1.閉包中不能修改外部作用域區域性變數

例項1:-每次呼叫閉包函式時都對變數a進行遞增的操作
#使用閉包時經典錯誤程式碼
def foo():
  a =    1 
  def bar():
    # nonlocal a
    a = a + 1 #修改外部作用域區域性變數
    #python規則指定所有在賦值語句左面的變數都是區域性變數
    return a
   return bar

c = foo()
print(c()) #錯誤

#解決方法
def foo():
  a = [1]
  def bar():
    a[0] = a[0] +    1
    return a[0]
  return bar
 2.2. 迴圈結束迴圈體中臨時變數i不會銷燬存在於執行環境中:
注意:1)返回閉包時:返回函式不要引用任何迴圈變數,或後續會發生變化變數。
2)如要引用迴圈變數
方法1:該函式建立一個引數,用這個引數繫結到迴圈變數 ;如例項  1
方法2:迭代 方法;參見例項   2
再建立一個函式,用該函式的引數繫結迴圈變數當前的值,
無論該迴圈變數後續如何更改,已繫結到函式引數的值不變   

 例項2: 

#python的函式只有在執行時,才會去找函式體裡的變數的值。
lst = []
for i in range(3):
   def foo(x):
     print (x + i)
   lst.append(foo)

for f in lst:
   f(2) #4,4,4

#解決方法
for i in range(3):
   def foo(x,y=i):
     print( x + y)
   lst.append(foo)  

 例項3 : 

def  count():
   lst = []
   for i in range(1, 4):
     def f():
       return i*i
     lst.append(f)
       return lst

f1, f2, f3 = count()
f1(),f2(),f3() #9,9,9

#解決辦法
def count():
   def f(j):
     def g():
       return j*j
     return g

   lst = []
   for i in range(1, 4):
      lst.append(f(i)) # f(i)立刻被執行,因此i的當前值被傳入f()
      return lst

#  結果:
f1, f2, f3 = count()
f1(),f2(),f3() #1,4,9

 3.作用
閉包主要是在函式式開發過程中使用。
用途1 

當閉包執行完後,仍然能夠保持住當前的執行環境。
希望函式每次執行結果基於這個函式上次的執行結果。

例棋盤遊戲:
棋盤大小為50*50,左上角為座標系原點(0,0),我需要一個函式,接收2個引數,分別為方向(direction),
步長(step),該函式控制棋子的運動。棋子運動的新的座標除了依賴於方向和步長以外,當然還要根據原
來所處的座標點,用閉包就可以保持住這個棋子原來所處的座標。

origin = [0, 0] # 座標系統原點
legal_x = [0, 50] # x軸方向的合法座標
legal_y = [0, 50] # y軸方向的合法座標

def create(pos=origin):
   def player(direction,step):
      # 先判斷引數direction,step合法性,direction不能斜走step不能為負
      # 對新生成的x,y座標合法性判斷處理,
      new_x = pos[0] + direction[0]*step
      new_y = pos[1] + direction[1]*step
      pos[0] = new_x
      pos[1] = new_y #注意!此處不能寫成 pos = [new_x, new_y]
      return pos
   return player

player = create() # 建立棋子player,起點為原點
print player([1,0],10) # 向x軸正方向移動10步
print player([0,1],20) # 向y軸正方向移動20步
print player([-1,0],10) # 向x軸負方向移動10步

輸出:
[10, 0]
[10, 20]
[0, 20]

 用途2: 

閉包可以根據外部作用域的區域性變數來得到不同的結果,這有點像一種類似配置功能的作用,
我們可以修改外部的變數,閉包根據這個變數展現出不同的功能。
比如有時我們需要對某些檔案的特殊行進行分析,先要提取出這些特殊行。
def make_filter(keep):
    def the_filter(file_name):
        file = open(file_name)
        lines = file.readlines()
        file.close()
        filter_doc = [i for i in lines if keep in i]
        return filter_doc
    
    return the_filter
    

# 取得檔案"result.txt"中含有"pass"關鍵字的行
filter = make_filter("pass")
filter_result = filter("result.txt")