小編給大家分享一下Python中作用域規(guī)則和閉包的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
目前創(chuàng)新互聯(lián)公司已為成百上千的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、綿陽(yáng)服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、衡東網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶(hù)導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶(hù)和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。Python中的作用域
假設(shè)在交互式命令行中定義如下的函數(shù):
>>> a = 1 >>> def foo(): b = 2 c = 3 print "locals: %s" % locals() return "result: %d" % (a + b +c) >>> a = 1 >>> def foo(): b = 2 c = 3 print "locals: %s" % locals() return "result: %d" % (a + b +c)
上述代碼先給a賦值1,緊接著定義了一個(gè)函數(shù):foo()。在函數(shù)foo()中我們定義了兩個(gè)整數(shù)b和c,函數(shù)的返回值為a、b、c三個(gè)數(shù)的和。
對(duì)上述函數(shù)進(jìn)行驗(yàn)證:
# result >>> foo() locals: {'c': 3, 'b': 2} result: 6 # result >>> foo() locals: {'c': 3, 'b': 2} result: 6
根據(jù)驗(yàn)證的結(jié)果,foo()函數(shù)的返回值為6。上述的函數(shù)定義中只有b和c兩個(gè)變量的賦值,那調(diào)用函數(shù)是如何判斷a的值呢?這涉及到函數(shù)的作用域規(guī)則。本文摘錄《Python參考手冊(cè)(第4版)》中的相關(guān)論述:
每次執(zhí)行一個(gè)函數(shù)時(shí), 就會(huì)創(chuàng)建心得局部命名空間。該命名空間代表一個(gè)局部環(huán)境,其中包含函數(shù)參數(shù)的名稱(chēng)和在函數(shù)體內(nèi)賦值的變量名稱(chēng)。解析這些名稱(chēng)時(shí):
解釋器將首先搜索局部命名空間;
如果沒(méi)有找到匹配的名稱(chēng),它就會(huì)搜索全局命名空間(函數(shù)的全局命名空間始終是定義該函數(shù)的模塊);
如果解釋器在全局命名空間中也找不到匹配值,最終會(huì)檢查內(nèi)置命名空間;
如果在內(nèi)置命名空間中也找不到匹配值,就會(huì)引發(fā)NameError異常。
對(duì)應(yīng)于上面的例子,foo函數(shù)首先會(huì)在局部命名空間中找三個(gè)變量的匹配值。上述代碼中的locals()方法給出了foo函數(shù)局部命名空間的內(nèi)容??梢钥闯?,局部命名空間是一個(gè)字典,包含b和c的值,這是因?yàn)槲覀冊(cè)趂oo函數(shù)中定義了這兩個(gè)變量。然而,局部命名空間中不包含a的值,所以就需要在全局命名空間中尋找??梢允褂胈_globals__獲取一個(gè)函數(shù)的局部命名空間。
# foo函數(shù)的全局命名空間 >>> foo.__globals__ {'a': 1, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000004613518>, '__doc__': None} # foo函數(shù)的全局命名空間 >>> foo.__globals__ {'a': 1, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000004613518>, '__doc__': None}
foo函數(shù)的全局命名空間中包含了內(nèi)置函數(shù)模塊、foo函數(shù)、變量a以及其他的一些參數(shù)。由于在foo函數(shù)的全局命名空間中找到了變量a,foo函數(shù)便返回三個(gè)變量的和。
Python閉包
上述的Python作用域規(guī)則具有普遍性。然而,在Python中“一切皆對(duì)象”,函數(shù)也不例外。這也就是說(shuō)可以把函數(shù)當(dāng)作參數(shù)傳遞給其他的函數(shù),也可以放在數(shù)據(jù)結(jié)構(gòu)中,還可以作為函數(shù)的返回結(jié)果。在這種情況下,Python的作用域規(guī)則會(huì)發(fā)生什么變化呢?我們還是舉一個(gè)例子:
>>> def foo(): a = 1 def bar(): b = 2 c = 3 return a + b + c return bar >>> def foo(): a = 1 def bar(): b = 2 c = 3 return a + b + c return bar
在這個(gè)例子中,我們定義了一個(gè)函數(shù)foo,并對(duì)變量a賦值。不過(guò)與之前的例子不同的是,在函數(shù)foo中我們還嵌套了一個(gè)函數(shù)bar,并且還定義了兩個(gè)變量,這個(gè)函數(shù)是作為函數(shù)foo的返回值。根據(jù)上面的作用域規(guī)則,函數(shù)foo的局部作用域既不是函數(shù)bar的局部作用域,也不是它的全局作用域,那函數(shù)bar能否正確匹配變量a的值呢?我們我們來(lái)驗(yàn)證一下這個(gè)函數(shù)是否能夠正常運(yùn)行。
# 調(diào)用函數(shù)foo() >>> bar = foo() # 返回值bar是一個(gè)函數(shù) >>> bar <function bar at 0x00000000045F3588> # 調(diào)用bar() >>> bar() # 結(jié)果顯示為三個(gè)變量之和 6
以上的驗(yàn)證結(jié)果說(shuō)明,在上述嵌套的函數(shù)中,內(nèi)部函數(shù)可以正確地引用外部函數(shù)的變量,即使外部的函數(shù)已經(jīng)返回。
這種內(nèi)部函數(shù)的局部作用域中可以訪(fǎng)問(wèn)外部函數(shù)局部作用域中變量的行為,我們稱(chēng)為: 閉包。內(nèi)部函數(shù)可以訪(fǎng)問(wèn)外部函數(shù)變量的特點(diǎn)很像將外部函數(shù)的變量直接“打包”到內(nèi)部函數(shù)中一樣,我們也可以這樣理解閉包:將組成函數(shù)的語(yǔ)句以及執(zhí)行這些語(yǔ)句的環(huán)境“打包”在一起時(shí)得到的對(duì)象稱(chēng)為閉包。
和閉包相關(guān)的幾個(gè)對(duì)象
為了了解閉包是怎么實(shí)現(xiàn)內(nèi)部函數(shù)對(duì)外部函數(shù)變量的引用,還需要對(duì)閉包相關(guān)的幾個(gè)對(duì)象進(jìn)行介紹。關(guān)于這幾個(gè)對(duì)象會(huì)涉及到Python的底層實(shí)現(xiàn),本文中對(duì)此不加以詳述,可以參考以下文章:
不過(guò),為了直觀(guān)地說(shuō)明閉包的實(shí)現(xiàn)過(guò)程(不分析底層實(shí)現(xiàn)),這里先簡(jiǎn)單介紹以下code對(duì)象。code對(duì)象是指代碼對(duì)象,表示編譯成字節(jié)的的可執(zhí)行Python代碼,或者字節(jié)碼。它有幾個(gè)比較重要的屬性:
co_name:函數(shù)的名稱(chēng)
co_nlocals: 函數(shù)使用的局部變量的個(gè)數(shù)
co_varnames: 一個(gè)包含局部變量名字的元組
co_cellvars: 是一個(gè)元組,包含嵌套的函數(shù)所引用的局部變量的名字
co_freevars: 是一個(gè)元組,保存使用了的外層作用域中的變量名
co_consts: 是一個(gè)包含字節(jié)碼使用的字面量的元組
其中比較關(guān)鍵的是co_varnames和co_freevars兩個(gè)屬性。我們對(duì)上面的例子稍加修改:
Python
>>> def foo(): a = 1 b = 2 def bar(): return a + 1 def bar2(): return b + 2 return bar >>> bar = foo() # 外層函數(shù) >>> foo.func_code.co_cellvars ('a', 'b') >>> foo.func_code.co_freevars () # 內(nèi)層嵌套函數(shù) >>> bar.func_code.co_cellvars () >>> bar.func_code.co_freevars ('a',) >>> def foo(): a = 1 b = 2 def bar(): return a + 1 def bar2(): return b + 2 return bar >>> bar = foo() # 外層函數(shù) >>> foo.func_code.co_cellvars ('a', 'b') >>> foo.func_code.co_freevars () # 內(nèi)層嵌套函數(shù) >>> bar.func_code.co_cellvars () >>> bar.func_code.co_freevars ('a',)
以上說(shuō)明外層函數(shù)的code對(duì)象的co_cellvars保存了內(nèi)部嵌套函數(shù)需要引用的變量的名字,而內(nèi)層嵌套函數(shù)的code對(duì)象的co_freevars保存了需要引用外部函數(shù)作用域中的變量名字。具體來(lái)說(shuō),就是foo函數(shù)中嵌套了兩個(gè)函數(shù),它們都需要引用foo函數(shù)局部作用域中的變量,所以foo.func_code.co_cellvars便包含變量a和變量b的名稱(chēng)。而函數(shù)bar是foo的返回值,只引用了變量a,因此bar.func_code.co_freevars中便只包含變量a。
內(nèi)部函數(shù)和外部函數(shù)的co_freevars、co_cellvars的對(duì)應(yīng)關(guān)系,使得在函數(shù)編譯過(guò)程中內(nèi)部函數(shù)具有了一個(gè)閉包的特殊屬性__closure__(底層中對(duì)此有相關(guān)實(shí)現(xiàn))。__closure__屬性是一個(gè)由cell對(duì)象組成的元組,包含了由多個(gè)作用域引用的變量??梢宰鲆韵买?yàn)證:
>>> foo.__closure__ #None # 內(nèi)部函數(shù)bar對(duì)變量a的引用 >>> bar.__closure__ (<cell at 0x00000000044F6798: int object at 0x0000000003FA4B38>,) # 內(nèi)部函數(shù)bar引用的變量a的值 >>> bar.__closure__[0].cell_contents 1
看完了這篇文章,相信你對(duì)“Python中作用域規(guī)則和閉包的示例分析”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
名稱(chēng)欄目:Python中作用域規(guī)則和閉包的示例分析-創(chuàng)新互聯(lián)
文章起源:http://jinyejixie.com/article14/csdige.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、網(wǎng)站導(dǎo)航、全網(wǎng)營(yíng)銷(xiāo)推廣、建站公司、外貿(mào)建站、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容