首先:python
若是在一個函數的內部定義了另外一個函數,外部的咱們叫他外函數,內部的咱們叫他內函數。編程
而後:閉包
咱們來看看閉包的官方定義: 在一個外函數中定義了一個內函數,內函數裏運用了外函數的臨時變量,而且外函數的返回值是內函數的引用。這樣就構成了一個閉包。app
分析:編程語言
通常狀況下,在咱們認知當中,若是一個函數執行完畢,函數的內部(即名稱空間)全部東西都會釋放掉,被回收還給內存,局部變量也會消失。函數
可是閉包是一種特殊狀況,若是外函數在結束的時候發現其名稱空間(做用域)中的臨時變量未來會在內部函數中使用到,就把這個臨時變量綁定給了內部函數,而後本身再結束。spa
舉例說明:3d
# outer是外部函數 a和b都是外函數的臨時變量
def outer( a ):
b = 8
# inner是內函數
def inner(): #在內函數中 用到了外函數的臨時變量a和b
print(a+b) # 外函數的返回值是對內函數的引用
return inner if __name__ == '__main__': demo = outer(6) #調用外函數傳入參數6
#此時外函數兩個臨時變量 a是6 b是8 ,outer外函數調用執行,其返回值就是對內函數的引用,將引用的內函數inner的內存地址賦值存給demo
# 外函數結束的時候發現內部函數將會用到(由於demo只是引用了inner的內存地址,只有它加括號才能執行內部代碼,因此這裏說是將會用到)本身的臨時變量,這兩個臨時變量就不會釋放,會綁定給這個內部函數inner# 咱們調用內部函數,看一看內部函數是否是能使用外部函數的臨時變量
demo() # 14
# demo存了外函數的返回值,也就是inner函數的引用,這裏至關於執行inner函數
demo2 = outer(9) demo2() #17
若是要更全面的理解閉包就須要知道一些知識:code
python中一切都是對象,包括變量、函數,其實都是對象。對象
當咱們進行a=1的時候,其實是在內存當中開闢一片空間存放了值1,而後用a這個變量名存了1所在內存位置的引用(內存地址)。
a只不過是一個變量名字,a裏面存的是1這個數值所在的內存地址,就是a裏面存了數值1的引用。
相同的道理,在python中定義一個函數def demo(): 的時候,會在內存中開闢一塊空間,用於存放該函數的代碼、內部的局部變量等等。
這個demo只不過是一個變量名字,它裏面存了這個函數所在位置的引用而已。
咱們還能夠進行x = demo, y = demo, 這樣的操做就至關於,把demo裏存的東西賦值給x和y,這樣x 和y 都指向了demo函數所在的引用,在這以後咱們能夠用x() 或者 y() 來調用咱們本身建立的demo() ,調用的實際上根本就是一個函數,x、y和demo三個變量名存了同一個函數的引用。
有了上面對引用的解釋,就能夠更深入的理解「返回內函數的引用」這句話了。
對於閉包,在外函數outer中最後return了inner,咱們在調用外函數 demo = outer() 的時候,outer返回了inner,inner是一個函數的引用,這個引用被存入了demo中。因此接下來咱們再進行demo() 的時候,至關於運行了inner函數。
同時咱們知道,函數名後+括號就至關於調用執行了這個函數,若是不+括號,至關於只是一個函數的名字,裏面存了函數所在位置的引用。
在閉包內函數中,咱們能夠隨意使用外函數綁定來的臨時變量,可是正常事不能修改的,由於做用域不同,沒法修改
在基本的python語法當中,一個函數能夠隨意讀取全局數據,可是要修改全局數據的時候有兩種方法:一、global 聲明全局變量 二、全局變量是可變類型數據的時候能夠修改
在閉包內函數也是相似的狀況,在內函數中想修改閉包變量(外函數綁定給內函數的局部變量)的時候:
一、在python3中,能夠用nonlocal 關鍵字聲明 一個變量, 表示這個變量不是局部變量空間的變量,須要向上一層變量空間找這個變量。
二、在python2中,沒有nonlocal這個關鍵字,咱們能夠把閉包變量改爲可變類型數據進行修改,好比列表。
舉個栗子:
#修改閉包變量的實例 # outer是外部函數 a和b都是外函數的臨時變量
def outer( a ): b = 10 # a和b都是閉包變量
c = [a] #這裏對應修改閉包變量的方法2
# inner是內函數
def inner(): #內函數中想修改閉包變量
# 方法1 nonlocal關鍵字聲明
nonlocal b b+=1
# 方法二,把閉包變量修改爲可變數據類型 好比列表
c[0] += 1
print(c[0]) print(b) # 外函數的返回值是內函數的引用
return inner if __name__ == '__main__': demo = outer(5) demo() # 6 11
還有一點須要特別注意:
使用閉包的過程當中,一旦外函數被調用一次返回了內函數的引用,雖然每次調用內函數,是開啓一個函數執行事後消亡,可是閉包變量實際上只有一份,每次開啓內函數都在使用同一份閉包變量
def outer(x): def inner(y): nonlocal x x += y return x return inner a = outer(10) print(a(1)) # 11
print(a(3)) # 14(注意,這裏不是13,由於第二次使用的依然是x這個變量,只不過他在第一次引用的是第一次的結果11了)
兩次分別打印出11和14,因而可知,每次調用inner的時候,使用的閉包變量x其實是同一個。
閉包的做用:
一、裝飾器
二、面向對象:經歷了上面的分析,咱們發現外函數的臨時變量送給了內函數。這其實跟類對象的狀況同樣,對象有好多相似的屬性和方法,因此咱們建立類,用類建立出來的對象都具備相同的屬性方法。閉包也是實現面向對象的方法之一。在python當中雖然咱們不這樣用,在其餘編程語言入好比avaScript中,常常用閉包來實現面向對象編程
三、實現單例, 其實這也是裝飾器的應用。
def singleton(cls): _instance = cls('127.0.0.1', 3306) def wrapper(*args, **kwargs): if len(args) != 0 or len(kwargs) != 0: obj = cls(*args, **kwargs) return obj return _instance return wrapper @singleton # MySQL=singleton(MySQL) #MySQL=wrapper class MySQL: def __init__(self, ip, port): self.ip = ip self.port = port
四、本身還沒想到的其它...,歡迎補充^-^