在這裏咱們先學一個簡單的知識點。python
li = ['alex', '銀角', '女神', 'egon', '太白'] for i in enumerate(li): print(i) for index, name in enumerate(li, 1): print((index, name)) for index, name in enumerate(li, 100): # 起始位置默認是0,可更改 print((index, name)) ''' 輸出的結果爲: (0, 'alex') (1, '銀角') (2, '女神') (3, 'egon') (4, '太白') (1, 'alex') (2, '銀角') (3, '女神') (4, 'egon') (5, '太白') (100, 'alex') (101, '銀角') (102, '女神') (103, 'egon') (104, '太白') '''
下面講解閉包這個概念是參考博客太白金星。但願你們有不懂的地方能夠問我,能夠共同討論學習。安全
因爲閉包這個概念比較難以理解,尤爲是初學者來講,相對難以掌握,因此咱們經過示例去理解學習閉包。網絡
給你們提個需求,而後用函數去實現:完成一個計算不斷增長的系列值的平均值的需求。閉包
例如:整個歷史中的某個商品的平均收盤價。什麼叫平局收盤價呢?就是從這個商品一出現開始,天天記錄當天價格,而後計算他的平均值:平均值要考慮直至目前爲止全部的價格。app
好比大衆推出了一款新車:小白轎車。函數
第一天價格爲:100000元,平均收盤價:100000元工具
次日價格爲:110000元,平均收盤價:(100000 + 110000)/2 元學習
第三天價格爲:120000元,平均收盤價:(100000 + 110000 + 120000)/3 元優化
........設計
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) print(make_averager(120000))
從上面的例子能夠看出,基本上完成了咱們的要求,可是這個代碼相對來講是不安全的,由於你的這個series列表是一個全局變量,只要是全局做用域的任何地方,均可能對這個列表進行改變。
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) series.append(666) # 若是對數據進行相應改變,那麼你的平均收盤價就會出現很大的問題,數據沒有安全性。 print(make_averager(120000))
那麼怎麼辦呢?有人說,你把他放在函數中不就好了,這樣不就是局部變量了麼?數據不就相對安全了麼?
def make_averager(new_value): series = [] series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) # 100000.0 print(make_averager(110000)) # 110000.0 print(make_averager(120000)) # 120000.0
這樣計算的結果是不正確的,那是由於執行函數,會開啓一個臨時的名稱空間,隨着函數的結束而消失,因此你每次執行函數的時候,都是從新建立這個列表,那麼這怎麼作呢?這種狀況下,就須要用到咱們講的閉包了,咱們用閉包的思想改一下這個代碼。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() print(avg(100000)) print(avg(110000)) print(avg(120000))
你們仔細看一下這個代碼,我是在函數中嵌套了一個函數。那麼avg 這個變量接收的實際是averager函數名,也就是其對應的內存地址,我執行了三次avg 也就是執行了三次averager這個函數。那麼此時大家有什麼問題?
確定有學生就會問,那麼個人make_averager這個函數只是執行了一次,爲何series這個列表沒有消失?反而還能夠被調用三次呢?這個就是最關鍵的地方,也是閉包的精華所在。我給你們說一下這個原理,以圖爲證:
上面被紅色方框框起來的區域就是閉包,被藍色圈起來的那個變量應該是make_averager()函數的局部變量,它應該是隨着make_averager()函數的執行結束以後而消失。可是他沒有,是由於此區域造成了閉包,series變量就變成了一個叫自由變量的東西,averager函數的做用域會延伸到包含自由變量series的綁定。也就是說,每次我調用avg對應的averager函數 時,均可以引用到這個自用變量series,這個就是閉包。
閉包的定義:
閉包是嵌套在函數中的函數。
閉包必須是內層函數對外層函數的變量(非全局變量)的引用。
如何判斷判斷閉包?舉例讓同窗回答:
# 例一: def wrapper(): a = 1 def inner(): print(a) return inner ret = wrapper() # 例二: a = 2 def wrapper(): def inner(): print(a) return inner ret = wrapper() # 例三: def wrapper(a,b): def inner(): print(a) print(b) return inner a = 2 b = 3 ret = wrapper(a,b)
以上三個例子,最難判斷的是第三個,其實第三個也是閉包,若是咱們每次去研究代碼判斷其是否是閉包,有一些不科學,或者過於麻煩了,那麼有一些函數的屬性是能夠獲取到此函數是否擁有自由變量的,若是此函數擁有自由變量,那麼就能夠側面證實其是不是閉包函數了(瞭解):
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() # 函數名.__code__.co_freevars 查看函數的自由變量 print(avg.__code__.co_freevars) # ('series',) 固然還有一些參數,僅供瞭解: # 函數名.__code__.co_freevars 查看函數的自由變量 print(avg.__code__.co_freevars) # ('series',) # 函數名.__code__.co_varnames 查看函數的局部變量 print(avg.__code__.co_varnames) # ('new_value', 'total') # 函數名.__closure__ 獲取具體的自由變量對象,也就是cell對象。 # (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,) # cell_contents 自由變量具體的值 print(avg.__closure__[0].cell_contents) # []
閉包的做用:保存局部信息不被銷燬,保證數據的安全性(這是一個十分重要的做用)。
閉包的應用:
在使用裝飾器以前,咱們先開始瞭解一下開發封閉原則。那麼什麼是開發封閉原則呢?
# 開放封閉原則 # 開放:對代碼的拓展是開放的。更新地圖,加新槍,等等 # 封閉:對源碼的修改是封閉的。閃躲用q。就是一個功能,一個函數。 # 別人用赤手空拳打你,用機槍掃你,扔雷.....這個功能不會改變。
1.對擴展是開放的
咱們說,任何一個程序,不可能在設計之初就已經想好了全部的功能而且將來不作任何更新和修改。因此咱們必須容許代碼擴展、添加新功能。
2.對修改是封閉的
就像咱們剛剛提到的,由於咱們寫的一個函數,頗有可能已經交付給其餘人使用了,若是這個時候咱們對函數內部進行修改,或者修改了函數的調用方式,頗有可能影響其餘已經在使用該函數的用戶。
因此裝飾器最終最完美的定義就是:在不改變原被裝飾的函數的源代碼以及調用方式下,爲其添加額外的功能。
那麼裝飾器是什麼呢?
# 什麼叫作裝飾器 # 裝飾器:裝飾,裝修,房子原本能夠住,若是裝修,不影響你住 # 並且體驗更加,讓你的生活中增長了許多的功能,例如看電視,洗澡 # 器:工具 # 裝飾器:徹底遵循開放封閉原則。 # 裝飾器:在不改變原函數的代碼以及調用方式的前提下,爲其增長新的功能
代碼優化:語法糖
根據個人學習,咱們知道了,若是想要各給一個函數加一個裝飾器應該是這樣:
def home(name,age): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner home = timer(home) home('太白',18)
若是你想給home加上裝飾器,每次執行home以前你要寫上一句:home = timer(home)這樣你在執行home函數 home('太白',18) 纔是真生的添加了額外的功能。可是每次寫這一句也是很麻煩。因此,Python給咱們提供了一個簡化機制,用一個很簡單的符號去代替這一句話。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數的執行效率爲{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模擬一下網絡延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') home('太白',18)
你看此時我調整了一下位置,你要是不把裝飾器放在上面,timer是找不到的。home函數若是想要加上裝飾器那麼你就在home函數上面加上@home,就等同於那句話 home = timer(home)。這麼作沒有什麼特殊意義,就是讓其更簡單化,好比你在影視片中見過野戰軍的做戰時因爲不方便說話,用一些簡單的手勢表明一些話語,就是這個意思。
至此標準版的裝飾器就是這個樣子:
def wrapper(func): def inner(*args,**kwargs): '''執行被裝飾函數以前的操做''' ret = func(*args,**kwargs) '''執行被裝飾函數以後的操做''' return ret return inner
這個就是標準的裝飾器,徹底符合代碼開放封閉原則。這幾行代碼必定要背過,會用。