什麼是閉包
閉包(closure)是函數式編程的重要的語法結構。函數式編程是一種編程範式 (而面向過程編程和麪向對象編程也都是編程範式)。在面向過程編程中,我們見到過函數(function);在面向對象編程中,我們見過對象(object)。函數和對象的根本目的是以某種邏輯方式組織代碼,並提高代碼的可重複使用性(reusability)。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重複使用性。2
In conclusion here are the three criteria’s for a closure:1
閉包有如下三個特點
- There must be a nested function (a function inside another function).
必須有一個嵌套函數(一個函數在另一個函數中) - This nested function has to refer to a variable defined inside the enclosing function.
這個嵌套函數必須引用在封閉函數中定義的變量 - The enclosing function must return the nested function.
封閉函數必須返回嵌套函數
閉包的案例
關於什麼是閉包,看起來可能比較抽象,但是舉個例子就很容易理解。
#閉包函數的實例
#function_outside是外部函數,msg是外函數的臨時變量
def function_outside():
msg = 'Hello'
#function_inside是內函數
def function_inside():
#內函數中用到了外函數的臨時變量
return msg
#外函數的返回值是內函數的引用
return function_inside
function_outside是外部函數;在function_outside的內部,我們定義了一個function_inside,這個函數也被稱爲嵌套函數。符合第一個特點:必須有一個嵌套函數。
function_inside引用了function_outside函數中的臨時變量msg。符合第二個特點:這個嵌套函數必須引用在封閉函數中定義的變量。
return function_inside 返回了嵌套函數。符合第三個特點:封閉函數必須返回嵌套函數。
我們稱msg爲function_inside的環境變量,雖然function_inside引用了msg,但msg並不是定義在function_inside中的變量,而是定義在外部函數中。
在一個外函數(function_outside)中定義了一個內函數(function_inide),內函數裏引用了外函數的變量(msg),並且外函數的返回值是內函數的引用。這樣就構成了一個閉包。
- 在Python中,環境變量取值被保存在函數對象的__closure__屬性中。代碼如下:
def function_outside():
msg = 'Hello'
def function_inside():
return msg
return function_inside
my_function = function_outside()
print(my_function.__closure__)
print(my_function.__closure__[0].cell_contents)
__closure__裏包含了一個元組(tuple)。這個元組中的每個元素是cell類型的對象。我們看到第一個cell包含的就是字符串’hello’,也就是我們創建閉包時的環境變量msg的取值。
- 外函數的返回值是內函數的引用
關於這句話怎麼理解?
當我們在python中定義一個函數def demo():的時候,內存中會分配一些空間,存下這個函數的代碼、內部的變量等。
demo只不過是一個變量的名字,它裏面存了這個函數所在位置的引用而已。我們還可以進行x = demo,y = demo,這樣x和y都指向了demo函數所在的引用。
在這之後我們可以用x() 或者 y() 來調用我們自己創建的demo() ,調用的實際上根本就是一個函數,x、y和demo三個變量名存了同一個函數的引用。
有了上面的解釋,我們再來看“外函數的返回值是內函數的引用“這句話,就比較好理解了。如下截圖中,我們看見my_function的name返回的是function_inside這個函數的名稱。
def function_outside():
msg = 'Hello'
def function_inside():
return msg
return function_inside
my_function = function_outside()
print(my_function.__name__)
'''
運行結果:
function_inside
'''
使用閉包注意事項
- 閉包無法修改外包函數的局部變量
def function_outside():
msg = 'Hello'
def function_inside():
msg = 'Hi'
print('inner msg:',msg)
print('msg before call inner:',msg)
function_inside()
print('msg after call inner:',msg)
'''
運行結果:
msg before call inner: Hello
inner msg: Hi
msg after call inner: Hello
'''
從運行結果可以看出,雖然function_inside函數裏面也定義了一個局部變量msg,但是不會改變外部函數中的局部變量msg
要修改也很簡單,可以在內函數中用nonlocal關鍵字聲明,表示這個變量不是內部函數的變量,需要向上一層變量空間找這個變量
def function_outside():
msg = 'Hello'
def function_inside():
nonlocal msg
msg = 'Hi'
print('inner msg:',msg)
print('msg before call inner:',msg)
function_inside()
print('msg after call inner:',msg)
- 使用閉包的過程中,一旦外函數被調用一次返回了內函數的引用,雖然每次調用內函數,是開啓一個函數執行過後消亡,但是閉包變量實際上只有一份,每次開啓內函數都在使用同一份閉包變量
def outer(x):
def inner(y):
nonlocal x
x += y
return x
return inner
a = outer(10)
print(a(1))
print(a(3))
'''
運行結果:
11
14
'''
- 返回函數儘量不要引用循環變量或者會發生變化的變量
def outer():
result_list = []
for i in range(3):
def inner():
return i*i
result_list.append(inner)
return result_list
result = outer()
for item in result:
print(item())
'''
運行結果:
4
4
4
'''
我們希望是返回0,1,4,但是結果確是4,4,4.原因就是當把函數加入result_list列表的時候,i還沒有執行。等函數執行的時候,再去找i的值,這個時候i的值已經是2,所以結果是4,4,4
如果一定要引用循環變量怎麼辦?方法是用函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變:
def outer():
result_list = []
for i in range(3):
def inner(x = i):
return x*x
result_list.append(inner)
return result_list
result = outer()
for item in result:
print(item())
'''
運行結果:
0
1
4
'''
參考
- [1] Python Closures
- [2] Python深入04 閉包
- [3] Python中的閉包實例詳解
- [5] 函數式編程初探
文章也發佈於我的博客,歡迎大家閱讀。