Python基礎(十三)
- 2019 年 10 月 3 日
- 筆記
今日主要內容
- 閉包
- 裝飾器初識
- 標準裝飾器
一、閉包
(一)什麼是閉包
-
閉包:內層函數調用外層函數的變量就是閉包(不能是全局變量)
def func1(): a = 10 def func2(): print(a) # 內層函數調用外層函數的變量,這就是一個閉包 func1()
-
檢測閉包的方法
函數名.__closure__
- 若返回對象地址就是一個閉包,返回None就不是一個閉包
- 注意:
.__closure__
檢測的是函數名,判斷這個函數是否是閉包
def func1(): a = 10 def func2(): print(a) # 調用外層變量a,是閉包 print(func2.__closure__) # 判斷func2是否是閉包 func1() 運行結果: (<cell at 0x00000230F21874F8: int object at 0x0000000063258190>,)
def func1(): a = 10 def func2(): a = 10 print(a) # 使用自身函數變量a,不是閉包 print(func2.__closure__) func1() 運行結果: None
-
如何在全局空間中調用內部函數
- 外層函數返回內層函數的函數名(內存地址),就可在全局空間中調用內部函數
def func1(): a = 10 def func2(): print(a) return func2 # 返回內層函數的函數名 f = func1() # 此時f就是func2 f() # 調用內層函數func2 print(f.__closure__) 運行結果: 10 (<cell at 0x00000280A87574F8: int object at 0x0000000063258190>,)
(二)閉包的作用
- 保護數據安全
- 說白話一點,如果數據放在全局變量中(頂格寫代碼),數據的安全性很低,因為所有人都可以無意間修改它,想解決這個問題我們可以把數據放到函數中,只要不調用這個函數,我的數據就是安全的。但是有個問題,每次執行函數完,解釋器就會自動清空此函數開闢的局部空間中所有內容(包括數據開闢的空間),所以每次調用完函數,我的數據就沒了,此時就需要利用到了閉包。閉包可以在內層函數調用外層函數中的變量,而且內層函數如果調用了外層函數中的變量,那這個變量將不會消亡,將會常駐內存。所以,我們只需要在全局空間中調用這個閉包的內層函數,就可以使用我的數據了,而且數據的安全性也提高了。
-
將變量常駐內存,供後續代碼使用
def outer(): lst = [1,2,3,4,5] def inner(): return lst return inner f = outer() f_lst = f() print(f_lst) 運行結果: [1,2,3,4,5]
(三)閉包的應用
- 防止數據被誤修改
- 裝飾器(與閉包格式相同)
二、裝飾器初識
(一)軟件開發的六大原則(了解)
- 開閉原則(Open Close Principle)——裝飾器依據
- 里氏代換原則(Liskov Substitution Principle)
- 依賴倒轉原則(Dependence Inversion Principle)
- 接口隔離原則(Interface Segregation Principle)
- 迪米特法則(最少知道原則)(Demeter Principle)
- 合成復用原則(Composite Reuse Principle)
(二)裝飾器依據——開閉原則
- 開放封閉原則:
- 對功能擴展開放
- 對源碼修改封閉
(三)裝飾器引入
-
相信大多數人都玩LOL,我們模擬一次遊戲過程:
- 向平常一樣的流程,十連跪…
def play_lol(): print("登陸遊戲") print("開始排位...") print("遊戲中...") print("失敗...") print("結束遊戲") play_lol() 運行結果: 登陸遊戲 開始排位... 遊戲中... Virtory 結束遊戲
- 受不了了,開一個外掛吧,此時我的函數需要擴展,在前後添加開啟關閉外掛的功能
def play_lol(): print("開啟外掛!") # 添加開啟外掛功能 print("登陸遊戲") print("開始排位...") print("遊戲中...") print("勝利!!!") print("結束遊戲") print("關閉外掛!") # 添加關閉外掛功能 play_lol() 運行結果: 開啟外掛! 登陸遊戲 開始排位... 遊戲中... 勝利!!! 結束遊戲 關閉外掛!
- 但此時,違背了開閉原則,對源代碼進行了修改。想一個方法,對源代碼進行前後包裝,不改變源碼
def play_lol(): print("登陸遊戲") print("開始排位...") print("遊戲中...") print("勝利!!!") print("結束遊戲") def new_play(): # 將上面代碼前後包裝成了一個新的函數,沒有改變源碼 print("開啟外掛!") play_lol() print("關閉外掛!") new_play() 運行結果: 開啟外掛! 登陸遊戲 開始排位... 遊戲中... 勝利!!! 結束遊戲 關閉外掛!
- 功能實現了,而且還沒有改變源碼,但是有一個問題,我們之前訪問調用的是
play_lol
這個函數,但此時我們訪問調用的是new_play()
這個函數,相當於改變了調用,還是違背了開閉原則,沒有達到擴展的效果,此時我們就需要對這段代碼稍作變化
def play_lol(): print("登陸遊戲") print("開始排位...") print("遊戲中...") print("勝利!!!") print("結束遊戲") def wrapper(fn): # 裝飾器雛形 def inner(): print("開啟外掛!") fn() print("關閉外掛!") return inner func = wrapper(play_lol) # 調用裝飾器函數將我基礎函數傳入進去包裝 play_lol = func # 返回的是包裝函數,將包裝函數重命名成我原來的函數 play_lol() # 調用此函數 運行結果: 開啟外掛! 登陸遊戲 開始排位... 遊戲中... 勝利!!! 結束遊戲 關閉外掛!
- 上述代碼就引出了裝飾器的雛形,刨析一下:
- 裝飾器雛形:與閉包的格式相同,兩層函數構成:
- 內層函數就是我的包裝函數,將擴展的功能和原函數包在一起組成一個函數
- 外層函數的作用就是給內層函數傳參用的,傳入的是我原函數的函數名,在內層調用
- 裝飾器的返回值
return inner
:裝飾器的返回值是內層函數的函數名,真正進行包裝擴展的是內層函數 - 將返回值重命名成原函數名:將返回值重命名成原來的函數名,其實就是把真正作用的包裝函數重命名成原來的函數名,所以就解決了調用新函數的問題,真正遵循了開閉原則,再次調用原來的函數其實真正運行的是裝飾器內部的inner函數
- 裝飾器雛形:與閉包的格式相同,兩層函數構成:
def wrapper(fn): # 裝飾器雛形 def inner(): print("開啟外掛!") fn() print("關閉外掛!") return inner func = wrapper(play_lol) # 調用裝飾器函數將我基礎函數傳入進去包裝 play_lol = func # 返回的是包裝函數,將包賺函數重命名成我原來的函數 play_lol() # 調用此函數
-
利用語法糖裝飾
- 使用裝飾器的兩行代碼可以轉換成語法糖
func = wrapper(play_lol) # 調用裝飾器函數將我基礎函數傳入進去包裝 play_lol = func # 返回的是包裝函數,將包賺函數重命名成我原來的函數
- 語法糖
@wrapper # 語法糖 def play_lol(): print("登陸遊戲") print("開始排位...") print("遊戲中...") print("勝利!!!") print("結束遊戲")
-
此時,裝飾器的雛形就出來了
-
裝飾器雛形
def wrapper(fn): def inner(): """擴展功能""" fn() """擴展功能""" return inner @wrapper def func(): pass func()
-
遊戲模擬繼續進行
- 就算開掛,我們也得選完英雄,才能進入遊戲,所以我們給基礎函數傳個參數,但是我們真正執行的是裝飾器內部的inner包裝函數,所以也要給inner傳入參數
def wrapper(fn): # 裝飾器雛形 def inner(hero): # 套到裝飾器中內層包裝函數參數 print("開啟外掛!") fn(hero) # 基礎函數參數 print("關閉外掛!") return inner @wrapper def play_lol(hero): # 基礎函數參數 print("登陸遊戲") print("開始排位...") print(f"選擇英雄:{hero}") print("遊戲中...") print("勝利!!!") print("結束遊戲") play_lol("蓋倫") 運行結果: 開啟外掛! 登陸遊戲 開始排位... 選擇英雄:蓋倫 # 傳入的參數 遊戲中... 勝利!!! 結束遊戲 關閉外掛!
- 雖然開掛了,但是還是會遇到巨坑無敵坑的隊友,我們得把他記下來舉報他,所以要給基礎函數填寫返回值,同時給真正執行的inner函數填寫返回值
def wrapper(fn): # 裝飾器雛形 def inner(hero): print("開啟外掛!") ret = fn(hero) # 接收基礎函數的返回值 print("關閉外掛!") return ret # 返回包裝後的函數的返回值 return inner @wrapper def play_lol(hero): # 基礎函數參數 print("登陸遊戲") print("開始排位...") print(f"選擇英雄:{hero}") print("遊戲中...") print("勝利!!!") print("結束遊戲") return "坑比隊友:xxx" # 基礎函數返回值 print(play_lol("蓋倫")) 運行結果: 開啟外掛! 登陸遊戲 開始排位... 選擇英雄:蓋倫 遊戲中... 勝利!!! 結束遊戲 關閉外掛! 坑比隊友:xxx # 返回值
- 到此,我們裝飾器標準模式也就出來了
-
裝飾器標準模式(非常重要)
def wrapper(fn): def inner(*args, **kwargs): """擴展功能""" ret = fn(*args, **kwargs) """擴展功能""" return ret return inner @wrapper def func(): pass func()