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