Python教程: 闭包及陷阱
Python語(yǔ)言是支持函數(shù)式編程的,我們可以在一個(gè)函數(shù)的函數(shù)體中定義另一個(gè)完整的函數(shù),甚至返回這個(gè)函數(shù).在函數(shù)內(nèi)部定義的函數(shù)和外部定義的函數(shù)是相同的,唯一的區(qū)別就是在函數(shù)內(nèi)部定義的函數(shù)是不能被外部訪問(wèn)的.
1. 閉包的概念
閉包(Closure):內(nèi)層函數(shù)引用了外層函數(shù)的變量(包括它的參數(shù)),然后返回內(nèi)層函數(shù)的情況,這就是閉包.
在通過(guò)Python的語(yǔ)言介紹一下,一個(gè)閉包就是你調(diào)用了一個(gè)函數(shù)A,這個(gè)函數(shù)A返回了一個(gè)函數(shù)B給你。這個(gè)返回的函數(shù)B就叫做閉包。你在調(diào)用函數(shù)A的時(shí)候傳遞的參數(shù)就是自由變量。
2. 為什么使用閉包
基于上面的介紹,不知道讀者有沒(méi)有感覺(jué)這個(gè)東西和類有點(diǎn)相似,相似點(diǎn)在于他們都提供了對(duì)數(shù)據(jù)的封裝。不同的是閉包本身就是個(gè)方法。和類一樣,我們?cè)诰幊虝r(shí)經(jīng)常會(huì)把通用的東西抽象成類,(當(dāng)然,還有對(duì)現(xiàn)實(shí)世界——業(yè)務(wù)的建模),以復(fù)用通用的功能。閉包也是一樣,當(dāng)我們需要函數(shù)粒度的抽象時(shí),閉包就是一個(gè)很好的選擇。
在這點(diǎn)上閉包可以被理解為一個(gè)只讀的對(duì)象,你可以給他傳遞一個(gè)屬性,但它只能提供給你一個(gè)執(zhí)行的接口。因此在程序中我們經(jīng)常需要這樣的一個(gè)函數(shù)對(duì)象——閉包,來(lái)幫我們完成一個(gè)通用的功能,比如后面會(huì)提到的——裝飾器
3. 使用閉包
第一種場(chǎng)景 ,在python中很重要也很常見(jiàn)的一個(gè)使用場(chǎng)景就是裝飾器,Python為裝飾器提供了一個(gè)很友好的“語(yǔ)法糖”——@,讓我們可以很方便的使用裝飾器,裝飾的原理不做過(guò)多闡述,簡(jiǎn)言之你在一個(gè)函數(shù)func上加上@decorator_func, 就相當(dāng)于decorator_func(func):
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' def decorator_func(func):def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@decorator_func def func(name):print('my name is', name)# 等價(jià)于 decorator_func(func)func("tracydzf")在裝飾器的這個(gè)例子中,閉包(wrapper)持有了外部的func這個(gè)參數(shù),并且能夠接受外部傳過(guò)來(lái)的參數(shù),接受過(guò)來(lái)的參數(shù)在原封不動(dòng)的傳給func,并返回執(zhí)行結(jié)果。
這是個(gè)簡(jiǎn)單的例子,稍微復(fù)雜點(diǎn)可以有多個(gè)閉包,比如經(jīng)常使用的那個(gè)LRUCache的裝飾器,裝飾器上可以接受參數(shù)@lru_cache(expire=500)這樣。實(shí)現(xiàn)起來(lái)就是兩個(gè)閉包的嵌套:
def lru_cache(expire=5):# 默認(rèn)5s超時(shí)def func_wrapper(func):def inner(*args, **kwargs):# cache 處理 bala bala balareturn func(*args, **kwargs)return innerreturn func_wrapper@lru_cache(expire=10*60) def get(request, pk):# 省略具體代碼return response()閉包的陷阱
先看代碼
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' # 希望一次返回3個(gè)函數(shù),分別計(jì)算1x1,2x2,3x3: def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() print(f1(), f2(), f3()) 得到的結(jié)果是 9 9 9Python語(yǔ)言的特性所致.當(dāng)count()函數(shù)返回三個(gè)函數(shù)時(shí),這三個(gè)變量所引用的變量i的值已經(jīng)變成了3,因?yàn)樵诜祷氐臅r(shí)候三個(gè)函數(shù)并沒(méi)有被調(diào)用,所以此時(shí)它們并沒(méi)有及時(shí)計(jì)算它們對(duì)應(yīng)的i乘以i的值,等到三個(gè)函數(shù)都返回時(shí),然后調(diào)用三個(gè)函數(shù),此時(shí)i的值已經(jīng)為3,計(jì)算i乘以i的值自然就都是9了.
所以在返回閉包的情況下,我們一定要注意的一點(diǎn)就是:
返回函數(shù)千萬(wàn)不要引用任何一個(gè)循環(huán)變量,或者在之后會(huì)發(fā)生改變的變量.
當(dāng)然對(duì)于這種情況我們還是有解決方法的.我們?cè)趦?nèi)層函數(shù)f()內(nèi)再定義一個(gè)內(nèi)層函數(shù)g(),用這個(gè)函數(shù)的參數(shù)綁定循環(huán)變量的當(dāng)前值,這樣的話,無(wú)論循環(huán)變量之后如何改變,每一次循環(huán)中的循環(huán)變量i的值就都保存在了第三層函數(shù)g()中,如此得到了我們期望的輸出結(jié)果.
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:778463939 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' def count():fs = []for i in range(1, 4):def f(j):def g():return j*jreturn gfs.append(f(i))return fsf1, f2, f3 = count()print(f1(), f2(), f3()) 程序的執(zhí)行結(jié)果:1 4 9如果嫌這段代碼過(guò)于臃腫,可以考慮使用lambda表達(dá)式進(jìn)行縮減.
def create_multipliers():return [lambda x, i=i : i * x for i in range(5)]for i in create_multipliers():print(i(2)) 與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Python教程: 闭包及陷阱的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python之一行代码
- 下一篇: python教程:super()的用法