Python函數(shù)編程——閉包和裝飾器
一、閉包
關(guān)于閉包,即函數(shù)定義和函數(shù)表達式位于另一個函數(shù)的函數(shù)體內(nèi)(嵌套函數(shù))。而且,這些內(nèi)部函數(shù)可以訪問它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)。當其中一個這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時,就會形成閉包。也就是說,內(nèi)部函數(shù)會在外部函數(shù)返回后被執(zhí)行。而當這個內(nèi)部函數(shù)執(zhí)行時,它仍然必需訪問其外部函數(shù)的局部變量、參數(shù)以及其他內(nèi)部函數(shù)。這些局部變量、參數(shù)和函數(shù)聲明(最初時)的值是外部函數(shù)返回時的值,但也會受到內(nèi)部函數(shù)的影響。
def outer():
name = 'alex'
def inner():
print("在inner里打印外層函數(shù)的變量",name)
return inner # 注意這里只是返回inner的內(nèi)存地址,并未執(zhí)行
f = outer() # .inner at 0x1027621e0>
f() # 相當于執(zhí)行的是inner()
注意此時outer已經(jīng)執(zhí)行完畢,正常情況下outer里的內(nèi)存都已經(jīng)釋放了,但此時由于閉包的存在,我們卻還可以調(diào)用inner, 并且inner內(nèi)部還調(diào)用了上一層outer里的name變量。這種粘粘糊糊的現(xiàn)象就是閉包。
閉包的意義: 返回的函數(shù)對象,不僅僅是一個函數(shù)對象,在該函數(shù)外還包裹了一層作用域,這使得,該函數(shù)無論在何處調(diào)用,優(yōu)先使用自己外層包裹的作用域。
閉包在哪會用? 請往下看。
二、裝飾器
請看完下面這個故事!!!!
你是一家視頻網(wǎng)站的后端開發(fā)工程師,你們網(wǎng)站有以下幾個版塊。
def home():
print("---首頁----")
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
def henan():
print("----河南專區(qū)----")
視頻剛上線初期,為了吸引用戶,你們采取了免費政策,所有視頻免費觀看,迅速吸引了一大批用戶,免費一段時間后,每天巨大的帶寬費用公司承受不了了,所以準備對比較受歡迎的幾個版塊收費,其中包括“歐美” 和 “河南”專區(qū),你拿到這個需求后,想了想,想收費得先讓其進行用戶認證,認證通過后,再判定這個用戶是否是VIP付費會員就可以了,是VIP就讓看,不是VIP就不讓看就行了唄。 你覺得這個需求很是簡單,因為要對多個版塊進行認證,那應(yīng)該把認證功能提取出來單獨寫個模塊,然后每個版塊里調(diào)用 就可以了,與是你輕輕的就實現(xiàn)了下面的功能 。
account = {
"is_authenticated":False,# 用戶登錄了就把這個改成True
"username":"alex", # 假裝這是DB里存的用戶信息
"password":"abc123" # 假裝這是DB里存的用戶信息
}
def login():
if account["is_authenticated"] is False:
username = input("user:")
password = input("pasword:")
if username == account["username"] and password == account["password"]:
print("welcome login....")
account["is_authenticated"] = True
else:
print("wrong username or password!")
else:
print("用戶已登錄,驗證通過...")
def home():
print("---首頁----")
def america():
login() # 執(zhí)行前加上驗證
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
def henan():
login() # 執(zhí)行前加上驗證
print("----河南專區(qū)----")
home()
america()
henan()
此時你信心滿滿的把這個代碼提交給你的TEAM LEADER審核,沒成想,沒過5分鐘,代碼就被打回來了, TEAM LEADER給你反饋是,我現(xiàn)在有很多模塊需要加認證模塊,你的代碼雖然實現(xiàn)了功能,但是需要更改要加認證的各個模塊的源代碼,這直接違反了軟件開發(fā)中的一個原則“開放-封閉”原則,簡單來說,它規(guī)定已經(jīng)實現(xiàn)的功能代碼不應(yīng)該被修改,但可以被擴展,即:
- 封閉:已實現(xiàn)的功能代碼塊不應(yīng)該被修改
- 開放:對現(xiàn)有功能的擴展開放
這個原則你還是第一次聽說,我擦,再次感受了自己這個野生程序員與正規(guī)軍的差距,BUT ANYWAY,老大要求的這個怎么實現(xiàn)呢?如何在不改原有功能代碼的情況下加上認證功能呢?你一時想不出思路,只好帶著這個問題回家繼續(xù)憋,媳婦不在家,去隔壁老王家串門了,你正好落的清靜,一不小心就想到了解決方案,不改源代碼可以呀。 你師從沙河金角大王時,記得他教過你,高階函數(shù),就是把一個函數(shù)當做一個參數(shù)傳給另外一個函數(shù),當時大王說,有一天,你會用到它的,沒想到這時這個知識點突然從腦子里蹦出來了,我只需要寫個認證方法,每次調(diào)用 需要驗證的功能 時,直接 把這個功能 的函數(shù)名當做一個參數(shù) 傳給 我的驗證模塊不就行了么,哈哈,機智如我,如是你啪啪啪改寫了之前的代碼。
account = {
"is_authenticated":False,# 用戶登錄了就把這個改成True
"username":"alex", # 假裝這是DB里存的用戶信息
"password":"abc123" # 假裝這是DB里存的用戶信息
}
def login(func):
if account["is_authenticated"] is False:
username = input("user:")
password = input("pasword:")
if username == account["username"] and password == account["password"]:
print("welcome login....")
account["is_authenticated"] = True
else:
print("wrong username or password!")
if account["is_authenticated"] is True: # 主要改了這
func() # 認證成功了就執(zhí)行傳入進來的函數(shù)
def home():
print("---首頁----")
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
def henan():
print("----河南專區(qū)----")
home()
login(america) # 需要驗證就調(diào)用 login,把需要驗證的功能 當做一個參數(shù)傳給login
login(henan)
你很開心,終于實現(xiàn)了老板的要求,不改變原功能代碼的前提下,給功能加上了驗證,此時,媳婦回來了,后面還跟著老王,你兩家關(guān)系非常好,老王經(jīng)常來串門,老王也是碼農(nóng),你跟他分享了你寫的代碼,興奮的等他看完 夸獎你NB,沒成想,老王看后,并沒有夸你,抱起你的兒子,笑笑說,你這個代碼還是改改吧, 要不然會被開除的,WHAT? 會開除,明明實現(xiàn)了功能 呀, 老王講,沒錯,功能是實現(xiàn)了,但是你又犯了一個大忌,什么大忌? 你改變了調(diào)用方式呀, 想一想,現(xiàn)在沒每個需要認證的模塊,都必須調(diào)用你的login()方法,并把自己的函數(shù)名傳給你,人家之前可不是這么調(diào)用的, 試想,如果有100個模塊需要認證,那這100個模塊都得更改調(diào)用方式,這么多模塊肯定不止是一個人寫的,讓每個人再去修改調(diào)用方式 才能加上認證,你會被罵死的。。。。 你覺得老王說的對,但問題是,如何即不改變原功能代碼,又不改變原有調(diào)用方式,還能加上認證呢? 你苦思了一會,還是想不出,老王在逗你的兒子玩,你說,老王呀,快給我點思路 ,實在想不出來,老王背對著你問,
老王:學過匿名函數(shù)沒有?
你:學過學過,就是lambda嘛
老王:那lambda與正常函數(shù)的區(qū)別是什么?
你:最直接的區(qū)別是,正常函數(shù)定義時需要寫名字,但lambda不需要
老王:沒錯,那lambda定好后,為了多次調(diào)用 ,可否也給它命個名?
你:可以呀,可以寫成plus = lambda x:x+1類似這樣,以后再調(diào)用plus就可以了,但這樣不就失去了lambda的意義了,明明人家叫匿名函數(shù)呀,你起了名字有什么用呢?
老王:我不是要跟你討論它的意義 ,我想通過這個讓你明白一個事實
老王邊說著,邊拿起你兒子的畫板,在上面寫了以下代碼:
def plus(n):
return n+1
plus2 = lambda x:x+1
老王: 上面這兩種寫法是不是代表 同樣的意思?
你:是的
老王:我給lambda x:x+1 起了個名字叫plus2,是不是相當于def plus2(x) ?
你:我擦,你別說,還真是,但老王呀,你想說明什么呢?
老王: 沒啥,只想告訴你,給函數(shù)賦值變量名就像def func_name 是一樣的效果,如下面的plus(n)函數(shù),你調(diào)用時可以用plus名,還可以再起個其它名字,如
calc = plus
calc(n)
你明白我想傳達什么意思了么?
你:。。。。。。。。。。。這。。。。。。嗯 。。。。。不太。。。。明白 。。
老王:。。。。這。。。。。呵呵。。。。。。好吧。。。。,那我在給你點一下,你之前寫的下面這段調(diào)用認證的代碼
home()
login(america) #需要驗證就調(diào)用 login,把需要驗證的功能 當做一個參數(shù)傳給login
# home()
# america()
login(henan)
老王:你之所以改變了調(diào)用方式,是因為用戶每次調(diào)用時需要執(zhí)行l(wèi)ogin(henan),類似的。其實稍一改就可以了呀
home()
america = login(america)
henan = login(henan)
老王:這樣以后其它人調(diào)用henan時,其實相當于調(diào)用了login(henan), 通過login里的驗證后,就會自動調(diào)用henan功能。
你:我擦,還真是唉。。。,老王,還是你nb。。。不過,等等, 我這樣寫了好,那用戶調(diào)用時,應(yīng)該是下面這個樣子
home()
america = login(america) #你在這里相當于把america這個函數(shù)替換了
henan = login(henan)
#那用戶調(diào)用時依然寫
america()
但問題在于,還不等用戶調(diào)用 ,你的america = login(america)就會先自己把america執(zhí)行了呀。。。。,你應(yīng)該等我用戶調(diào)用的時候再執(zhí)行才對呀,不信我試給你看。。。
老王:哈哈,你說的沒錯,這樣搞會出現(xiàn)這個問題,但你想想有沒有解決辦法呢?
你:我擦,你指的思路呀,大哥。。。我哪知道下一步怎么走。。。
老王:算了,估計你也想不出來。。。 學過嵌套函數(shù)沒有?
你:yes,然后呢?
老王:想實現(xiàn)一開始你寫的america = login(america)不觸發(fā)你真正的america函數(shù)的執(zhí)行,只需要在這個login里面再定義一層函數(shù),第一次調(diào)用america = login(america)只調(diào)用到外層login,這個login雖然會執(zhí)行,但不會觸發(fā)認證了,因為認證的所有代碼被封裝在login里層的新定義 的函數(shù)里了,login只返回 里層函數(shù)的函數(shù)名,這樣下次再執(zhí)行america()時, 就會調(diào)用里層函數(shù)啦。。。
你:。。。。。。什么? 什么個意思,我蒙逼了。。。
老王:還是給你看代碼吧。。
account = {
"is_authenticated":False,# 用戶登錄了就把這個改成True
"username":"alex", # 假裝這是DB里存的用戶信息
"password":"abc123" # 假裝這是DB里存的用戶信息
}
def login(func):
def inner(): # 再定義一層函數(shù)
if account["is_authenticated"] is False:
username = input("user:")
password = input("pasword:")
if username == account["username"] and password == account["password"]:
print("welcome login....")
account["is_authenticated"] = True
else:
print("wrong username or password!")
if account["is_authenticated"] is True:
func()
return inner # 注意這里只返回inner的內(nèi)存地址,不執(zhí)行
def home():
print("---首頁----")
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
def henan():
print("----河南專區(qū)----")
home()
america = login(america) # 這次執(zhí)行l(wèi)ogin返回的是inner的內(nèi)存地址 .inner at 0x101762840>
henan = login(henan) # .inner at 0x102562840>
america() # 相當于執(zhí)行inner()
henan()
此時你仔細著了老王寫的代碼 ,感覺老王真不是一般人呀,連這種奇淫巧技都能想出來。。。,心中默默感謝上天賜你一個大牛鄰居。
你: 老王呀,你這個姿勢很nb呀,你獨創(chuàng)的? 此時你媳婦噗嗤的笑出聲來,你也不知道 她笑個球。。。
老王:呵呵, 這不是我獨創(chuàng)的呀當然 ,這是開發(fā)中一個常用的玩法,叫語法糖,官方名稱“裝飾器”,其實上面的寫法,還可以更簡單 可以把下面代碼去掉
america = login(america) # 這次執(zhí)行l(wèi)ogin返回的是inner的內(nèi)存地址
henan = login(henan) # .inner at 0x102562840>
只在你要裝飾的函數(shù)上面加上下面代碼
@login
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
@login
def henan():
print("----河南專區(qū)----")
效果是一樣的。
你開心的玩著老王教你的新姿勢 ,玩著玩著就手賤給你的“河南專區(qū)”版塊 加了個參數(shù),然后,結(jié)果出錯了。。
你:老王,老王,怎么傳個參數(shù)就不行了呢?
老王:那必然呀,你調(diào)用henan時,其實是相當于調(diào)用的login,你的henan第一次調(diào)用時henan = login(henan), login就返回了inner的內(nèi)存地址,第2次用戶自己調(diào)用henan(5),實際上相當于調(diào)用的是inner,但你的inner定義時并沒有設(shè)置參數(shù),但你給他傳了個參數(shù),所以自然就報錯了呀
你:但是我的 版塊需要傳參數(shù)呀,你不讓我傳不行呀。。。
老王:沒說不讓你傳,稍做改動便可。。
老王:你再試試就好了 。
你: 果然好使,大神就是大神呀。 。。 不過,如果有多個參數(shù)呢?
老王:。。。。老弟,你不要什么都讓我教你吧,非固定參數(shù)你沒學過么?*args,**kwargs…
你:噢 。。。還能這么搞?,nb,我再試試。 你身陷這種新玩法中無法自拔,竟沒注意到老王已經(jīng)離開,你媳婦告訴你說為了不打擾你加班,今晚帶孩子去跟她姐妹住 ,你覺得媳婦真體貼,最終,你終于搞定了所有需求,完全遵循開放-封閉原則,最終代碼如下。
account = {
"is_authenticated":False,# 用戶登錄了就把這個改成True
"username":"alex", # 假裝這是DB里存的用戶信息
"password":"abc123" # 假裝這是DB里存的用戶信息
}
def login(func):
def inner(*args,**kwargs): # 再定義一層函數(shù)
if account["is_authenticated"] is False:
username = input("user:")
password = input("pasword:")
if username == account["username"] and password == account["password"]:
print("welcome login....")
account["is_authenticated"] = True
else:
print("wrong username or password!")
if account["is_authenticated"] is True:
func(*args,**kwargs)
return inner # 注意這里只返回inner的內(nèi)存地址,不執(zhí)行
def home():
print("---首頁----")
@login
def america():
print("----歐美專區(qū)----")
def japan():
print("----日韓專區(qū)----")
@login
def henan(vip_level):
if vip_level < 3:
print("----河南專區(qū)普通會員----")
else:
print("歡迎來到尊貴河南口音RMB玩家私密社區(qū)".center(50,"-"))
print("再充值500就可以獲取演員微信號,幸福大門即將開啟".center(50," "))
# home()
# america = login(america) # 這次執(zhí)行l(wèi)ogin返回的是inner的內(nèi)存地址 .inner at 0x101762840>
# henan = login(henan) # .inner at 0x102562840>
america() # 相當于執(zhí)行inner()
henan(5)
此時,你已累的不行了,洗洗就抓緊睡了,半夜,上廁所,隱隱聽到隔壁老王家有微弱的女人的聲音傳來,你會心一笑,老王這家伙,不聲不響找了女朋友也不帶給我看看,改天一定要見下真人。。。。。你本想著給媳婦打個電話問問她到了閨蜜家沒有,想著想著竟然睡著了。。。
翌日,你精神抖擻到了公司,把代碼扔給leader, leader看完后,會心一笑,小伙子不錯,這才是專業(yè)的寫法嘛。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
