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