Python系列之閉包

什麼是閉包

閉包(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),並且外函數的返回值是內函數的引用。這樣就構成了一個閉包。

  1. 在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的取值。

  1. 外函數的返回值是內函數的引用

關於這句話怎麼理解?

當我們在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
'''

使用閉包注意事項

  1. 閉包無法修改外包函數的局部變量
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)
  1. 使用閉包的過程中,一旦外函數被調用一次返回了內函數的引用,雖然每次調用內函數,是開啓一個函數執行過後消亡,但是閉包變量實際上只有一份,每次開啓內函數都在使用同一份閉包變量
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
'''
  1. 返回函數儘量不要引用循環變量或者會發生變化的變量
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
'''

參考

文章也發佈於我的博客,歡迎大家閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章