在Python語(yǔ)言中,可以在函數(shù)中定義函數(shù)。 這種在函數(shù)中嵌套定義的函數(shù)也叫內(nèi)部函數(shù)。我們來(lái)看下面的代碼:
成都創(chuàng)新互聯(lián)公司主營(yíng)莊浪網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,重慶App定制開發(fā),莊浪h5微信小程序搭建,莊浪網(wǎng)站營(yíng)銷推廣歡迎莊浪等地區(qū)企業(yè)咨詢
上述代碼中,定義了函數(shù)greet,在函數(shù)greet內(nèi)部又定義了一個(gè)函數(shù)inner_func, 并調(diào)用該函數(shù)打印了一串字符。
我們可以看到,內(nèi)部函數(shù)inner_func的定義和使用與普通函數(shù)基本相同。需要注意的是變量的作用域,在上述代碼中,函數(shù)參數(shù)name對(duì)于全局函數(shù)greet是局部變量,對(duì)內(nèi)部函數(shù)inner_func來(lái)說(shuō)則是非局部變量。內(nèi)部函數(shù)對(duì)于非局部變量的訪問(wèn)規(guī)則類似于標(biāo)準(zhǔn)的外部函數(shù)訪問(wèn)全局變量。
從這個(gè)例子我們還可以看到內(nèi)部函數(shù)的一個(gè)作用,就是通過(guò)定義內(nèi)部函數(shù)的方式將一些功能隱藏起來(lái),防止外部直接調(diào)用。常見的場(chǎng)景是,在一個(gè)復(fù)雜邏輯的函數(shù)中,將一些小的任務(wù)定義成內(nèi)部函數(shù),然后由這個(gè)外層函數(shù)使用,這樣可以使代碼更為清晰,易于維護(hù)。這些內(nèi)部函數(shù)只會(huì)在這個(gè)外層函數(shù)中使用,不能被其他函數(shù)或模塊使用。
在Python語(yǔ)言中, 函數(shù)也是對(duì)象,它可以被創(chuàng)建、賦值給變量,或者作為函數(shù)的返回值。我們來(lái)看下面這個(gè)例子。
在上述代碼中,在函數(shù)gen_greet內(nèi)部定義了inner_func函數(shù),并返回了一個(gè)inner_func函數(shù)對(duì)象。外部函數(shù)gen_greet返回了一個(gè)函數(shù)對(duì)象,所以像gen_greet這樣的函數(shù)也叫工廠函數(shù)。
在內(nèi)部函數(shù)inner_func中,使用了外部函數(shù)的傳參greet_words(非局部變量),以及函數(shù)的參數(shù)name(局部變量),來(lái)打印一個(gè)字符串。
接下來(lái),調(diào)用gen_greet("Hello")創(chuàng)建一個(gè)函數(shù)對(duì)象say_hello,緊接著調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hello, Mr. Zhang!
同樣的,調(diào)用gen_greet("Hi")創(chuàng)建一個(gè)函數(shù)對(duì)象say_hi,調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hi,Tony!
我們可以發(fā)現(xiàn),gen_greet返回的函數(shù)對(duì)象具有記憶功能,它能夠把所需使用的非局部變量保存下來(lái),用于后續(xù)被調(diào)用的時(shí)候使用。這種保存了非局部變量的函數(shù)對(duì)象被稱作閉包(closure)。
那么閉包是如何實(shí)現(xiàn)的呢?其實(shí)并不復(fù)雜,函數(shù)對(duì)象中有一個(gè)屬性__closure__,它就是在創(chuàng)建函數(shù)對(duì)象時(shí)用來(lái)保存這些非局部變量的。
__closure__屬性是一個(gè)元組或者None類型。在上述代碼中,我們可以通過(guò)下面方式查看:
函數(shù)的嵌套所實(shí)現(xiàn)的功能大都可以通過(guò)定義類的方式來(lái)實(shí)現(xiàn),而且類是更加面向?qū)ο蟮拇a編寫方式。
嵌套函數(shù)的一個(gè)主要用途是實(shí)現(xiàn)函數(shù)的裝飾器。我們看下面的代碼:
在上述代碼中,logger函數(shù)返回函數(shù)with_logging,with_logging則是打印了函數(shù)func的名稱及傳入的參數(shù),然后調(diào)用func, 并將參數(shù)傳遞給func。其中的@wraps(func)語(yǔ)句用于復(fù)制函數(shù)func的名稱、注釋文檔、參數(shù)列表等等,使得with_logging函數(shù)具有被裝飾的函數(shù)func相同的屬性。
代碼中接下來(lái)用@logger對(duì)函數(shù)power_func進(jìn)行修飾,它的作用等同于下面的代碼:
可見,裝飾器@符其實(shí)就是上述代碼的精簡(jiǎn)寫法。
通過(guò)了解了嵌套函數(shù)和閉包的工作原理,我們?cè)谑褂眠^(guò)程中就能夠更加得心應(yīng)手了。
之前 分析了裝飾器的語(yǔ)法,由此可以直接推導(dǎo)出其基本框架。但為了寫出一個(gè)功能完整的裝飾器,還需要了解一個(gè)概念——閉包。
閉包(closure) ,是引用了自由變量的函數(shù)。 這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。
看下面的例子
對(duì) f 內(nèi)部的函數(shù) g 來(lái)說(shuō),參數(shù) a 既不是它的參數(shù),也不是它的局部變量,而是它的自由變量。該自由變量可以
閉包和嵌套函數(shù)的概念有所區(qū)別。閉包當(dāng)然是嵌套函數(shù),但沒(méi)有引用自由變量的嵌套函數(shù)卻不是閉包。
Python 的函數(shù)有一個(gè)只讀屬性 __closure__ ,存儲(chǔ)的就是函數(shù)所引用的自由變量,
如果僅僅是嵌套函數(shù),它的 __closure__ 應(yīng)該是 None 。
閉包有個(gè)重要的特性:內(nèi)部函數(shù)只能引用而不能修改外部函數(shù)中定義的自由變量。試圖直接修改只有兩種結(jié)果,要么運(yùn)行出錯(cuò),要么你以為你修改了,實(shí)際并沒(méi)有。
不能修改不是因?yàn)?Python 設(shè)計(jì)者故意限制,不給它權(quán)限,而是外部的自由變量被內(nèi)部的局部變量覆蓋了;被覆蓋了也不是閉包獨(dú)有的特性,從普通函數(shù)內(nèi)部同樣也不能直接修改全局變量。Python 命名空間的查找規(guī)則簡(jiǎn)寫為 LEGB,四個(gè)字母分別代表 local、enclosed、global 和 build-in,閉包外層函數(shù)的命名空間就是 enclosed。Python 在檢索變量時(shí),按照 L - E - G - B 的順序依次查找,如果在 L 中找到了變量,就不會(huì)繼續(xù)向后查找。
在示例 1 中,你的本意是修改自由變量 number ,然而并不能:由于存在對(duì) number 的賦值語(yǔ)句( number += 1 ),Python 會(huì)認(rèn)為 number 是 printer 的局部變量,可是在局部變量字典中又查找不到它的定義,只好拋出異常。拋出的異常不是因?yàn)椴荒苄薷淖杂勺兞?,而是局部變量還沒(méi)賦值就被引用了。
在示例 2 中,Python 成功地在 printer 內(nèi)定義了局部變量 number ,并覆蓋了同名自由變量,你可能以為自己成功修改了 print_msg 中的 number ,然而并沒(méi)有。
怎么才能修改呢?
一種做法是利用可變類型(mutable)的特性,把變量存放在列表(List)之中。對(duì)可變的列表的修改并不需要對(duì)列表本身賦值, number[0] = 3 只是修改了列表元素。雖然列表發(fā)生了變化,但引用列表的變量卻并沒(méi)有改變,巧妙地“瞞”過(guò)了 Python。見示例3。
Python 3 引入了 nonlocal 關(guān)鍵字,明確告訴解釋器:這不是局部變量,要找上外頭找去。在示例 4 中, nonlocal 幫助我們實(shí)現(xiàn)了所期望的對(duì)自由變量的修改。
其實(shí),在 Python 2 中,用 global 代替 nonlocal ,也能達(dá)到類似的效果,但由于全局變量的不易控制,這種做法不被提倡。
下面的例子很好地展示了自由變量的特點(diǎn):與引用它的函數(shù)一同存在,而想要修改它,得小心謹(jǐn)慎。
裝飾器 rate_limit 的作用,是限制被裝飾的函數(shù)每秒內(nèi)最多被訪問(wèn) max_per_sec 次。為此,需要維護(hù)一個(gè)變量用以記錄上次被調(diào)用的時(shí)刻,它獨(dú)立于函數(shù)之外,和被修飾的函數(shù)一同存在,還能在每次被調(diào)用的時(shí)候更新。 last_time_called 就是這樣的變量。為了正確地更新, last_time_called 以列表的形式存在。如果在 Python 3 中,它也可以直接存為 float ,只要在內(nèi)部函數(shù)中聲明為 nonlocal ,也可以達(dá)到同樣的目的。
在函數(shù)中可以定義另一個(gè)函數(shù)時(shí),如果內(nèi)部的函數(shù)引用了外部的函數(shù)的變量,則可能產(chǎn)生閉包。
閉包可以用來(lái)在一個(gè)函數(shù)與一組私有變量之間創(chuàng)建關(guān)聯(lián)關(guān)系。
在給定函數(shù)被多次調(diào)用的過(guò)程中,這些私有變量能夠保持其持久性。
形成閉包的三個(gè)條件
必須有一個(gè)內(nèi)嵌函數(shù)—這對(duì)應(yīng)函數(shù)之間的嵌套;
內(nèi)嵌函數(shù)必須引用一個(gè)定義在閉合范圍內(nèi)的變量—內(nèi)部函數(shù)引用外部變量;
外部函數(shù)必須返回內(nèi)嵌函數(shù)—必須返回內(nèi)部函數(shù)。
換句話來(lái)說(shuō):閉包的概念很簡(jiǎn)單,一個(gè)可以引用在函數(shù)閉合范圍內(nèi)變量的函數(shù),即內(nèi)部函數(shù),只有那個(gè)內(nèi)部函數(shù)才有所謂的__closure__屬性。
閉包的原理
形成閉包之后,閉包函數(shù)會(huì)獲得一個(gè)非空的_Closure_屬性,這個(gè)屬性是一個(gè)元組。
組里面的對(duì)象為cell對(duì)象,而訪問(wèn)cell對(duì)象的cell_contents屬性則可以得到閉包變量的當(dāng)前值。
而隨著閉包的繼續(xù)調(diào)用,變量會(huì)進(jìn)行再次更新。由此可見,一般形成閉包之后,Python確定會(huì)將_closure_和閉包函數(shù)綁定作為儲(chǔ)存閉包變量的場(chǎng)所。
閉包的好處是什么?
其實(shí),閉包并不是必須的。
沒(méi)有閉包的話,Python的功能不會(huì)受到任何影響;但有了閉包之后,可以提供一種額外的解決方案。
在python中,函數(shù)可以被嵌套定義,也就是說(shuō),函數(shù)中可以定義函數(shù)。該函數(shù)還可以將其內(nèi)部定義的函數(shù)作為返回值返回。
閉包的定義:一般來(lái)說(shuō),我們可以認(rèn)為,如果一個(gè)函數(shù)可以讀取其他函數(shù)中的局部變量,那么它們就構(gòu)成了閉包。
注意 :閉包的定義不是特別清晰,但大體上的意思是這樣的。
我們知道,普通的函數(shù)是可以使用全局變量的
類似的,函數(shù)中定義的函數(shù),也是可以使用外部函數(shù)的變量的。因此,滿足了函數(shù)讀取了其他函數(shù)局部變量的這一條件,他們因此構(gòu)成了閉包。
在閉包的使用中,我們可以先給外部的函數(shù)賦予不同的局部變量,然后再調(diào)用其中內(nèi)部的函數(shù)時(shí),就可以讀取到這些不同的局部變量了。
外部變量的使用 在普通函數(shù)中,雖然可以直接使用全局變量,但是不可以直接修改全局變量。從變量的作用域來(lái)說(shuō),一旦你嘗試修改全局變量,那么就會(huì)嘗試創(chuàng)建并使用一個(gè)同名的局部變量。因此,如果你需要在普通函數(shù)中修改全局變量,需要使用global
同樣的,如果你希望通過(guò)定義在內(nèi)部的函數(shù)去修改其外部函數(shù)的變量,那么必須使用nonlocal
(1)unpack tuple和list, 可以讓函數(shù)返回多個(gè)值
def count():
return (1, 2, 3) # 或者 return [1, 2, 3]
# 把列表解包,把1 2 3 分別賦值給 a b c
a, b, c = count()
print a, b, c
# 輸出 1, 2, 3
(2)假設(shè)你知道Python的dict類型。Python中,在函數(shù)中定義一個(gè)變量的時(shí)候,會(huì)在一個(gè)隱藏的叫l(wèi)ocals的dict里面插入key-value,其中key是變量名,value是變量值。而引用一個(gè)變量的時(shí)候,則首先會(huì)在這個(gè)叫l(wèi)ocals的dict里面,根據(jù)變量名作為key,去查對(duì)應(yīng)的值。
var = 1 # 你可以認(rèn)為這里進(jìn)行了 locals['var'] = 1 的操作
print var # 在對(duì)var變量進(jìn)行求值的時(shí)候,就在locals['var']里面找var變量對(duì)應(yīng)的值
(3)for循環(huán)中,每次循環(huán)只是給 `i` 重新綁定值
for i in (1, 2, 3):
print i
print i
# 一次輸入 1 2 3 3
每次`for i in (1, 2, 3)`相當(dāng)于在`print i`之前,進(jìn)行了
`locals['i'] = 1`
`locals['i'] = 2`
`locals['i'] = 3`
的操作
所以最后的`print i`再去locals字典里面找`i`的時(shí)候,就變成 3 了。
(4)閉包是 一個(gè)函數(shù)加上這個(gè)函數(shù)引用的外部變量
var = 1
def f():
print var
# 這里的閉包是函數(shù) f 和 f 引用的外部變量 var
def count():
var2 = 2
def f():
print var2
# 這里的閉包是函數(shù) f 和 f 引用的外部變量 var2
return f
拿第一個(gè)函數(shù) f 來(lái)說(shuō)。在 f 運(yùn)行的時(shí)候,解釋器拿著'var'這個(gè)字符串去locals字典里面找,發(fā)現(xiàn)找不到,于是在closure字典里面找,最后closure字典里面找,你可以認(rèn)為就是找closure['var'],然后發(fā)現(xiàn)找到對(duì)應(yīng)的值。count里面的 f 函數(shù)同理。
(為了容易理解,我這里說(shuō)謊了。實(shí)際上 f 壓根沒(méi)有closure,count里面的 f 才有。其實(shí)closure壓根不是像locals那樣的字典)
(5)函數(shù)定義時(shí),函數(shù)只是記錄變量的名字。
要區(qū)分什么是名字,什么是值。
`i = 1`這里 i 只是名字,只是一個(gè)字符串 'i' 。這句話運(yùn)行完,locals['i'] = 1,就說(shuō) i 對(duì)應(yīng)的值是1
def count():
fs = []
for i in range(1, 4):
# 定義一個(gè)函數(shù),等價(jià)于運(yùn)行了 locals['f'] = 真正生成的函數(shù)
# 每次循環(huán),這里都會(huì)重新生成一個(gè)函數(shù),然后把重新生成的函數(shù)賦值給 locals['f']
def f():
return i * i # 引用了'i'這個(gè)名字,但并不是引用了'i'對(duì)應(yīng)的值
# 等價(jià)于 locals['fs'].append(locals['f'])
# f 不是函數(shù),它只是一個(gè)名字'f'。f 引用的東西,也就是locals['f']才是真正的函數(shù)
fs.append(f)
# 于是這個(gè)for循環(huán)生成了三個(gè)函數(shù),這三個(gè)函數(shù)是沒(méi)有名字的,這個(gè)函數(shù)運(yùn)行完后,它們跟'f'這個(gè)名字就毛關(guān)系都沒(méi)有了(是的我說(shuō)慌了,但可以先不管)
# 把整個(gè)列表返回,這個(gè)列表包含了三個(gè)函數(shù)
return fs
# count()返回三個(gè)函數(shù)的列表,unpack 列表的語(yǔ)法把列表中的三個(gè)函數(shù)抽出來(lái),重新給他們命名為 f1, f2, f3
# 也就是說(shuō),
# locals['f1'] = 列表中的第1個(gè)函數(shù)
# locals['f2'] = 列表中的第2個(gè)函數(shù)
# locals['f3'] = 列表中的第3個(gè)函數(shù)
# 這三個(gè)函數(shù)跟'f'這個(gè)名字現(xiàn)在毛關(guān)系都沒(méi)有。(其實(shí)是有的,但為了說(shuō)明需要簡(jiǎn)化,現(xiàn)在你可以完全不管括號(hào)里面說(shuō)的話)
f1, f2, f3 = count()
print f1(), f2(), f3()
# 好了我們運(yùn)行它們,輸入都是 9
# def f():
# return i * i
這是因?yàn)?f1 現(xiàn)在對(duì)應(yīng)的函數(shù),里面引用了 'i' 這個(gè)字符串,我們根據(jù) 'i '這個(gè)字符串去找它對(duì)應(yīng)的值,先找到 f 當(dāng)前的locals字典,發(fā)現(xiàn)沒(méi)有,因?yàn)楹瘮?shù)定義的時(shí)候沒(méi)有定義 i 變量。然后再去closure['i']里面找,因?yàn)镻ython是通過(guò)closure字典實(shí)現(xiàn)閉包的(就當(dāng)它是對(duì)的好不好),所以我們可以在closure['i']找到值,這個(gè)值就是我們上一次運(yùn)行的時(shí)候count函數(shù)里面殘留的locals['i'],而由于for循環(huán)三遍之后,locals['i'] == 3,所以找到 i 的值就是3。所以最后輸出都是9
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
如在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)“。在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的橋梁。
閉包包含自由(未綁定到特定對(duì)象)變量,這些變量不是在這個(gè)代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)。
“閉包” 一詞來(lái)源于以下兩者的結(jié)合:要執(zhí)行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對(duì)象沒(méi)有被釋放)和為自由變量提供綁定的計(jì)算環(huán)境(作用域)。
在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等語(yǔ)言中都能找到對(duì)閉包不同程度的支持。
集合 S 是閉集當(dāng)且僅當(dāng) Cl(S)=S(這里的cl即closure,閉包)。特別的,空集的閉包是空集,X 的閉包是 X。集合的交集的閉包總是集合的閉包的交集的子集(不一定是真子集)。有限多個(gè)集合的并集的閉包和這些集合的閉包的并集相等;
零個(gè)集合的并集為空集,所以這個(gè)命題包含了前面的空集的閉包的特殊情況。無(wú)限多個(gè)集合的并集的閉包不一定等于這些集合的閉包的并集,但前者一定是后者的父集。
網(wǎng)頁(yè)題目:python閉包函數(shù)定義 python閉包的概念
文章來(lái)源:http://chinadenli.net/article36/dodcssg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供手機(jī)網(wǎng)站建設(shè)、用戶體驗(yàn)、網(wǎng)站改版、網(wǎng)站收錄、云服務(wù)器、企業(yè)網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)