日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

一文搞懂:词法作用域、动态作用域、回调函数、闭包

發(fā)布時(shí)間:2023/12/19 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文搞懂:词法作用域、动态作用域、回调函数、闭包 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

把以前一直只限于知道,卻不清晰理解的這幾個(gè)概念完完整整地梳理了一番。內(nèi)容參考自wiki頁(yè)面,然后加上自己一些理解。

詞法作用域和動(dòng)態(tài)作用域

不管什么語(yǔ)言,我們總要學(xué)習(xí)作用域(或生命周期)的概念,比如常見(jiàn)的稱呼:全局變量、包變量、模塊變量、本地變量、局部變量等等。不管如何稱呼這些作用域的范圍,實(shí)現(xiàn)它們的目的都一樣:

  • (1)為了避免名稱沖突;
  • (2)為了限定變量的生命周期(本文以變量名說(shuō)事,其它的名稱在規(guī)則上是一樣的)。

但是不同語(yǔ)言的作用域規(guī)則不一樣,雖然學(xué)個(gè)簡(jiǎn)單的基礎(chǔ)就足夠應(yīng)用,因?yàn)槲覀冇芯幊桃?guī)范:(1)盡量避免名稱沖突;(2)加上類似于local的修飾符盡量縮小生效范圍;(3)放進(jìn)代碼塊,等等。但是真正去細(xì)心驗(yàn)證作用域的生效機(jī)制卻并非易事(我學(xué)Python的時(shí)候,花了很長(zhǎng)時(shí)間細(xì)細(xì)驗(yàn)證,學(xué)perl的時(shí)候又花了很長(zhǎng)時(shí)間細(xì)細(xì)驗(yàn)證),但可以肯定的是,理解本文的詞法作用域規(guī)則(Lexical scoping)和動(dòng)態(tài)作用域規(guī)則(dynamic scoping),對(duì)學(xué)習(xí)任何語(yǔ)言的作用域規(guī)則都有很大幫助,這兩個(gè)規(guī)則是各種語(yǔ)言都宏觀通用的。

很簡(jiǎn)單的一段bash下的代碼:

x=1 function g(){ echo "g: $x" ; x=2; } function f(){ local x=3 ; g; echo "f: $x"; } # 輸出2還是3 f # 輸出1還是3? echo $x # 輸出1還是2?

對(duì)于bash來(lái)說(shuō),上面輸出的分別是3(g函數(shù)中echo)、2(f函數(shù)中的echo)和1(最后一行echo)。但是同樣語(yǔ)義的代碼在其它語(yǔ)言下得到的結(jié)果可能就不一樣(分別輸出1、3和2,例如perl中將local替換為my)。

這牽扯到兩種貫穿所有程序語(yǔ)言的作用域概念:詞法作用域(類似于C語(yǔ)言中static)和動(dòng)態(tài)作用域。詞法作用域和"詞法"這個(gè)詞真的沒(méi)什么關(guān)系,反而更應(yīng)該稱之為"文本段作用域"。要區(qū)別它們,只需要回答"函數(shù)out_func中嵌套的內(nèi)層函數(shù)in_func能否看見(jiàn)out_func中的環(huán)境"。

對(duì)于上面的bash代碼來(lái)說(shuō),假如這段代碼是適用于所有語(yǔ)言的偽代碼:

  • 對(duì)于詞法作用域的語(yǔ)言,執(zhí)行f時(shí)會(huì)調(diào)用g,g將無(wú)法訪問(wèn)f文本段的變量,詞法作用域認(rèn)為g并不是f的一部分,而是跳出f的,因?yàn)間的定義文本段是在全局范圍內(nèi)的,所以它是全局文本段的一部分。如果函數(shù)g的定義文本段是在f內(nèi)部,則g屬于f文本段的一部分
    • 所以g不知道f文本段中l(wèi)ocal x=3的設(shè)置,于是g的echo會(huì)輸出全局變量x=1,然后設(shè)置x=2,因?yàn)樗鼪](méi)有加上作用域修飾符,而g又是全局內(nèi)的函數(shù),所以x將修改全局作用域的x值,使得最后的echo輸出2,而f中的echo則輸出它自己文本段中的local x=3。所以整個(gè)流程輸出1 3 2
  • 對(duì)于動(dòng)態(tài)作用域的語(yǔ)言,執(zhí)行f時(shí)會(huì)調(diào)用g,g將可以訪問(wèn)f文本中的變量,動(dòng)態(tài)作用域認(rèn)為g是f文本段的一部分,是f中的嵌套函數(shù)
    • 所以g能看到local x=3的設(shè)置,所以g的echo會(huì)輸出3。g中設(shè)置x=2后,僅僅只是在f的內(nèi)層嵌套函數(shù)中設(shè)置,所以x=2對(duì)g文本段和f文本段(因?yàn)間是f的一部分)都可見(jiàn),但對(duì)f文本段外部不可見(jiàn),所以f中的echo輸出2,最后一行的echo輸出1。所以整個(gè)流程輸出3 2 1
  • 總結(jié)來(lái)說(shuō):
    • 詞法作用域是關(guān)聯(lián)在編譯期間的,對(duì)于函數(shù)來(lái)說(shuō)就是函數(shù)的定義文本段的位置決定這個(gè)函數(shù)所屬的范圍
    • 動(dòng)態(tài)作用域是關(guān)聯(lián)在程序執(zhí)行期間的,對(duì)于函數(shù)來(lái)說(shuō)就是函數(shù)執(zhí)行的位置決定這個(gè)函數(shù)所屬的范圍

由于bash實(shí)現(xiàn)的是動(dòng)態(tài)作用域規(guī)則。所以,輸出的是3 2 1。對(duì)于perl來(lái)說(shuō),my修飾符實(shí)現(xiàn)詞法作用域規(guī)則,local修飾符實(shí)現(xiàn)動(dòng)態(tài)作用域規(guī)則。

例如,使用my修飾符的perl程序:

#!/usr/bin/perl$x=1; sub g { print "g: $x\n"; $x=2; } sub f { my $x=3; g(); print "f: $x\n"; } # 詞法作用域 f(); print "$x\n";

執(zhí)行結(jié)果:

[fairy@fairy:/perlapp]$ perl scope2.pl g: 1 f: 3 2

使用local修飾符的perl程序:

#!/usr/bin/perl$x=1; sub g { print "g: $x\n"; $x=2; } sub f { local $x=3; g(); print "f: $x\n"; } # 動(dòng)態(tài)作用域 f(); print "$x\n";

執(zhí)行結(jié)果:

[fairy@fairy:/perlapp]$ perl scope2.pl g: 3 f: 2 1

有些語(yǔ)言只支持一種作用域規(guī)則,特別是那些比較現(xiàn)代化的語(yǔ)言,而有些語(yǔ)言支持兩種作用域規(guī)則(正如perl語(yǔ)言,my實(shí)現(xiàn)詞法變量作用域規(guī)則,local實(shí)現(xiàn)動(dòng)態(tài)作用域規(guī)則)。相對(duì)來(lái)說(shuō),詞法作用域規(guī)則比較好控制整個(gè)流程,還能借此實(shí)現(xiàn)更豐富的功能(如最典型的"閉包"以及高階函數(shù)),而動(dòng)態(tài)作用域由于讓變量生命周期"沒(méi)有任何深度"(回想一下shell腳本對(duì)函數(shù)和作用域的控制,非常傻瓜化),比較少應(yīng)用上,甚至有些語(yǔ)言根本不支持動(dòng)態(tài)作用域。

閉包和回調(diào)函數(shù)

理解閉包、回調(diào)函數(shù)不可不知的術(shù)語(yǔ)

1.引用(reference):數(shù)據(jù)對(duì)象和它們的名稱

前文所說(shuō)的可見(jiàn)、不可見(jiàn)、變量是否存在等概念,都是針對(duì)變量名(或其它名稱,如函數(shù)名、列表名、hash名)而言的,和變量的值無(wú)關(guān)。名稱和值的關(guān)系是引用(或指向)關(guān)系,賦值的行為就是將值所在的數(shù)據(jù)對(duì)象的引用(指針)交給名稱,讓名稱指向這個(gè)內(nèi)存中的這個(gè)數(shù)據(jù)值對(duì)象。如下圖:

2.一級(jí)函數(shù)(first-class functions)和高階函數(shù)(high-order functions)

有些語(yǔ)言認(rèn)為函數(shù)就是一種類型,稱之為函數(shù)類型,就像變量一樣。這種類型的語(yǔ)言可以:

  • 將函數(shù)賦值給某個(gè)變量,那么這個(gè)變量就是這個(gè)函數(shù)體的另一個(gè)引用,就像是第二個(gè)函數(shù)名稱一樣。通過(guò)這個(gè)函數(shù)引用變量,可以找到函數(shù)體,然后調(diào)用執(zhí)行。
    • 例如perl中$ref_func=\&myfunc表示將函數(shù)myfunc的引用賦值給$ref_func,那么$ref_func也指向這個(gè)函數(shù)。
  • 將函數(shù)作為另一個(gè)函數(shù)的參數(shù)。例如兩個(gè)函數(shù)名為myfunc和func1,那么myfunc(func1)就將func1作為myfunc的參數(shù)。
    • 這種行為一般用于myfunc函數(shù)中對(duì)滿足某些邏輯的東西執(zhí)行func1函數(shù)。
    • 舉個(gè)簡(jiǎn)單的例子,unix下的find命令,將find看作是一個(gè)函數(shù),它用于查找指定路徑下符合條件的文件名,將-print、-exec {}\;選項(xiàng)實(shí)現(xiàn)的功能看作是其它的函數(shù)(請(qǐng)無(wú)視它是否真的是函數(shù)),這些選項(xiàng)對(duì)應(yīng)的函數(shù)是find函數(shù)的參數(shù),每當(dāng)find函數(shù)找到符合條件的文件名時(shí),就執(zhí)行-print函數(shù)輸出這個(gè)文件名
  • 函數(shù)的返回值也可以是另一個(gè)函數(shù)。例如myfunc函數(shù)的定義語(yǔ)句為function myfunc(){ ...return func1 }。
  • 其實(shí),實(shí)現(xiàn)上面三種功能的函數(shù)稱之為一級(jí)函數(shù)或高階函數(shù),其中高階函數(shù)至少要實(shí)現(xiàn)上面的2和3。一級(jí)函數(shù)和高階函數(shù)并沒(méi)有區(qū)分的必要,但如果一定要區(qū)分,那么:

    • 一級(jí)函數(shù)更像是一種術(shù)語(yǔ)概念,它將函數(shù)當(dāng)作一種值看待,可以將其賦值出去、作為參數(shù)傳遞出去以及作為返回值,對(duì)于計(jì)算機(jī)程序語(yǔ)言而言,它更多的是用來(lái)描述某種語(yǔ)言是否支持一級(jí)函數(shù);
    • 高階函數(shù)是一種函數(shù)類型,就像回調(diào)函數(shù)一樣,當(dāng)某個(gè)函數(shù)符合高階函數(shù)的特性,就可以將其稱之為這是一個(gè)高階函數(shù)。

    3.自由變量(free variable)和約束變量(bound variable)

    這是一組數(shù)學(xué)界的術(shù)語(yǔ)。

    在計(jì)算機(jī)程序語(yǔ)言中,自由變量是指函數(shù)中的一種特殊變量,這種變量既不在本函數(shù)中定義,也不是本函數(shù)的參數(shù)。換句話說(shuō),可能是外層函數(shù)中定義的但卻在內(nèi)層函數(shù)中使用的,所以自由變量常常和"非本地變量"(non-local variable,熟悉Python的人肯定知道)互用。例如:

    function func1(x){var z;function func2(y){return x+y+z # x和z既不是func2內(nèi)部定義的,也不是func2的參數(shù),所以x和z都是自由變量}return func1 }

    自由變量和約束變量對(duì)應(yīng)。所謂約束變量,是指這個(gè)變量之前是自由變量,但之后會(huì)對(duì)它進(jìn)行賦值,將自由變量綁定到一個(gè)值上之后,這個(gè)變量就成為約束變量或者稱為綁定變量。

    例如:

    function func1(x){var m=20 # 對(duì)func2來(lái)說(shuō),這是自由變量,對(duì)其賦值,所以m變成了bound variablevar zfunction func2(y){z=10 # 對(duì)自由變量z賦值,z變成bound variablereturn m+x+y+z # m、x和z都是自由變量}return func1 }ref_func=func1(3) # 對(duì)x賦值,x變成bound variable

    回調(diào)函數(shù)

    回調(diào)函數(shù)一開(kāi)始是C里面的概念,它表示的是一個(gè)函數(shù):

    • 可以訪問(wèn)另一個(gè)函數(shù)
    • 當(dāng)這個(gè)函數(shù)執(zhí)行完了,會(huì)執(zhí)行另一個(gè)函數(shù)

    也就是說(shuō),將一個(gè)函數(shù)(B)作為參數(shù)傳遞給另一個(gè)函數(shù)(A),但A執(zhí)行完后,再自動(dòng)調(diào)用B。所以這種回調(diào)函數(shù)的概念也稱為"call after"。

    但是現(xiàn)在回調(diào)函數(shù)已經(jīng)足夠通用化了。通用化的回調(diào)函數(shù)定義為:將函數(shù)B作為另一個(gè)函數(shù)A的參數(shù),執(zhí)行到函數(shù)A中某個(gè)地方的時(shí)候去調(diào)用B。和原來(lái)的概念相比,不再是函數(shù)A結(jié)束后再調(diào)用,而是我們自己定義在哪個(gè)地方調(diào)用。

    例如,Perl中的File::Find模塊中的find函數(shù),通過(guò)這個(gè)函數(shù)加上回調(diào)函數(shù),可以實(shí)現(xiàn)和unix find命令相同的功能。例如,搜索某個(gè)目錄下的文件,然后print輸出這個(gè)文件名,即find /path xxx -print。

    #!/usr/bin/perl use File::Find;sub print_path { # 定義一個(gè)函數(shù),用于輸出路徑名稱print "$File::Find::name\n"; }$callback = \&print_path; # 創(chuàng)建一個(gè)函數(shù)引用,名為$callback,所以perl是一種支持一級(jí)函數(shù)的語(yǔ)言find( $callback,"/tmp" ); # 查找/tmp下的文件,每查找到一個(gè)文件,就執(zhí)行一次$callback函數(shù)

    這里傳遞給find函數(shù)的$callback就是一個(gè)回調(diào)函數(shù)。幾個(gè)關(guān)鍵點(diǎn):

    • $callback作為參數(shù)傳遞給另一個(gè)find()函數(shù)(所以find()函數(shù)是一個(gè)高階函數(shù))
    • 在find()函數(shù)中,每查找到一個(gè)文件,就調(diào)用一次這個(gè)$callback函數(shù)。當(dāng)然,如果find是我們自己寫的程序,就可以由我們自己定義在什么地方去調(diào)用$callback
    • $callback不是我們主動(dòng)調(diào)用的,而是由find()函數(shù)在某些情況下(每查找到一個(gè)文件)去調(diào)用的

    回調(diào)就像對(duì)函數(shù)進(jìn)行填空答題一樣,根據(jù)我們填入的內(nèi)容去復(fù)用填入的函數(shù)從而實(shí)現(xiàn)某一方面的細(xì)節(jié),而普通函數(shù)則是定義了就只能機(jī)械式地復(fù)用函數(shù)本身。

    之所以稱為回調(diào)函數(shù),是因?yàn)檫@個(gè)函數(shù)并非由我們主觀地、直接地去調(diào)用,而是將函數(shù)作為一個(gè)參數(shù),通過(guò)被調(diào)用者間接去調(diào)用這個(gè)函數(shù)參數(shù)。本質(zhì)上,回調(diào)函數(shù)和一般的函數(shù)沒(méi)有什么區(qū)別,可能只是因?yàn)槲覀兌x一個(gè)函數(shù),卻從來(lái)沒(méi)有直接調(diào)用它,這一點(diǎn)感覺(jué)上有點(diǎn)奇怪,所以有人稱之為"回調(diào)函數(shù)",用來(lái)統(tǒng)稱這種間接的調(diào)用關(guān)系。

    回調(diào)函數(shù)可以被多線程異步執(zhí)行。

    徹底搞懂閉包

    計(jì)算機(jī)中的閉包概念是從數(shù)學(xué)世界引入的,在計(jì)算機(jī)程序語(yǔ)言中,它也稱為詞法閉包、函數(shù)閉包。

    閉包簡(jiǎn)單的、通用的定義是指:函數(shù)引用一個(gè)詞法變量,在函數(shù)或語(yǔ)句塊結(jié)束后(變量的名稱消失),詞法變量仍然對(duì)引用它的函數(shù)有效。在下一節(jié)還有關(guān)于閉包更嚴(yán)格的定義(來(lái)自wiki)。

    看一個(gè)python示例:函數(shù)f中嵌套了函數(shù)g,并返回函數(shù)g

    def f(x):def g(y):return x + yreturn g # 返回一個(gè)閉包:有名稱的函數(shù)(高階函數(shù)的特性)# 將執(zhí)行函數(shù)時(shí)返回的閉包函數(shù)賦值給變量(高階函數(shù)的特性) a = f(1)# 調(diào)用存儲(chǔ)在變量中閉包函數(shù) print (a(5))# 無(wú)需將閉包存儲(chǔ)進(jìn)臨時(shí)變量,直接一次性調(diào)用閉包函數(shù) print( f(1)(5) ) # f(1)是閉包函數(shù),因?yàn)闆](méi)有將其賦值給變量,所以f(1)稱為"匿名閉包"

    上面的a是一個(gè)閉包,它是函數(shù)g()的一個(gè)實(shí)例。f()的參數(shù)x可以被g訪問(wèn),在f()返回g函數(shù)后,f()就退出了,隨之消失的是變量名x(注意是變量名稱x,變量的值在這里還不一定會(huì)消失)。當(dāng)將閉包f(1)賦值給a后,原來(lái)x指向的數(shù)據(jù)對(duì)象(即數(shù)值1)仍被a指向的閉包函數(shù)引用著,所以x對(duì)應(yīng)的值1在x消失后仍保存在內(nèi)存中,只有當(dāng)名為a的閉包被消除后,原來(lái)x指向的數(shù)值1才會(huì)消失。

    閉包特性1:對(duì)于返回的每個(gè)閉包g()來(lái)說(shuō),不同的g()引用不同的x對(duì)應(yīng)的數(shù)據(jù)對(duì)象。換句話說(shuō),變量x對(duì)應(yīng)的數(shù)據(jù)對(duì)象對(duì)每個(gè)閉包來(lái)說(shuō)都是相互獨(dú)立的

    例如下面得到兩個(gè)閉包,這兩個(gè)閉包中持有的自由變量雖然都引用相等的數(shù)值1,但兩個(gè)數(shù)值是不同數(shù)據(jù)對(duì)象,這兩個(gè)閉包也是相互獨(dú)立的:

    a=f(1) b=f(1)

    閉包特性2:對(duì)于某個(gè)閉包函數(shù)來(lái)說(shuō),只要這不是一個(gè)匿名閉包,那么閉包函數(shù)可以一直訪問(wèn)x對(duì)應(yīng)的數(shù)據(jù)對(duì)象,即使名稱x已經(jīng)消失

    但是

    a=f(1) # 有名稱的閉包a,將一直引用數(shù)值對(duì)象1 a(3) # 調(diào)用閉包函數(shù)a,將返回1+3=4,其中1是被a引用著的對(duì)象,即使a(3)執(zhí)行完了也不放開(kāi) a(3) # 再次調(diào)用函數(shù)a,將返回4,其中1和上面一條語(yǔ)句的1是同一個(gè)數(shù)據(jù)對(duì)象 f(1)(3) # 調(diào)用匿名的閉包函數(shù),數(shù)據(jù)對(duì)象1在f(1)(3)執(zhí)行完就消失 f(1)(3) # 調(diào)用匿名的閉包函數(shù),和上面的匿名閉包是相互獨(dú)立的

    最重要的特性就在于上面執(zhí)行的兩次a(3):將詞法變量的生命周期延長(zhǎng),但卻足夠安全

    看下面perl程序中的閉包函數(shù),可以更直觀地看到結(jié)果。

    sub how_many { # 定義函數(shù)my $count=2; # 詞法變量$countreturn sub {print ++$count,"\n"}; # 返回一個(gè)匿名函數(shù),這是一個(gè)匿名閉包 }$ref=how_many(); # 將閉包賦值給變量$refhow_many()->(); # (1)調(diào)用匿名閉包:輸出3 how_many()->(); # (2)調(diào)用匿名閉包:輸出3 $ref->(); # (3)調(diào)用命名閉包:輸出3 $ref->(); # (4)再次調(diào)用命名閉包:輸出4

    上面將閉包賦值給$ref,通過(guò)$ref去調(diào)用這個(gè)閉包,則即使how_many中的$count在how_many()執(zhí)行完就消失了,但$ref指向的閉包函數(shù)仍然在引用這個(gè)變量,所以多次調(diào)用$ref會(huì)不斷修改$count的值,所以上面(3)和(4)先輸出3,然后輸出改變后的4。而上面(1)和(2)的輸出都是3,因?yàn)閮蓚€(gè)how_many()函數(shù)返回的是獨(dú)立的匿名閉包,在語(yǔ)句執(zhí)行完后數(shù)據(jù)對(duì)象3就消失了。

    閉包更嚴(yán)格的定義

    注意,嚴(yán)格定義的閉包和前面通俗定義的閉包結(jié)果上是不一樣的,通俗意義上的閉包并不一定符合嚴(yán)格意義上的閉包。

    關(guān)于閉包更嚴(yán)格的定義,是一段誰(shuí)都看不懂的說(shuō)明(來(lái)自wiki)。如下,幾個(gè)關(guān)鍵詞我加粗顯示了,因?yàn)橹匾?/p>

    閉包是一種在支持一級(jí)函的編程語(yǔ)言中能夠?qū)⒃~法作用域中的變量名稱進(jìn)行綁定的技術(shù)。在操作上,閉包是一種用于保存函數(shù)和環(huán)境的記錄。這個(gè)環(huán)境記錄了一些關(guān)聯(lián)性的映射,將函數(shù)的每個(gè)自由變量與創(chuàng)建閉包時(shí)所綁定名稱的值或引用相關(guān)聯(lián)通過(guò)閉包,就算是在作用域外部調(diào)用函數(shù),也允許函數(shù)通過(guò)閉包拷貝他們的值或通過(guò)引用的方式去訪問(wèn)那些已經(jīng)被捕獲的變量

    我知道這段話誰(shuí)都看不懂,所以簡(jiǎn)而言之一下:一個(gè)函數(shù)實(shí)例和一個(gè)環(huán)境結(jié)合起來(lái)就是閉包。這個(gè)所謂的環(huán)境,決定了這個(gè)函數(shù)的特殊性,決定了閉包的特性。

    還是上面的python示例:函數(shù)f中嵌套了函數(shù)g,并返回函數(shù)g

    def f(x):def g(y):return x + yreturn g # 返回一個(gè)閉包:有名稱的函數(shù)# 將執(zhí)行函數(shù)時(shí)返回的閉包函數(shù)賦值給變量 a = f(1)

    上面的a是一個(gè)閉包,它是函數(shù)g()的一個(gè)實(shí)例。f()的參數(shù)x可以被g訪問(wèn),對(duì)于g()來(lái)說(shuō),這個(gè)x不是g()內(nèi)部定義的,也不是g()的參數(shù),所以這個(gè)x對(duì)于g來(lái)說(shuō)是一個(gè)自由變量(free variable)。雖然g()中持有了自由變量,但是g()函數(shù)自身不是閉包函數(shù),只有在g持有的自由變量x和傳遞給f()函數(shù)的x的值(即f(1)中的1)進(jìn)行綁定的時(shí)候,才會(huì)從g()創(chuàng)建一個(gè)閉包函數(shù),這表示閉包函數(shù)開(kāi)始引用這個(gè)自由變量,并且這個(gè)閉包一直持有這個(gè)變量的引用,即使f()已經(jīng)執(zhí)行完畢了。然后在f()中return這個(gè)閉包函數(shù),因?yàn)檫@個(gè)閉包函數(shù)綁定了(引用)自由變量x,這就是閉包函數(shù)所在的環(huán)境。

    環(huán)境對(duì)閉包來(lái)說(shuō)非常重要,是區(qū)別普通函數(shù)和閉包的關(guān)鍵。如果返回的每個(gè)閉包不是獨(dú)立持有屬于自己的自由變量,而是所有閉包都持有完全相同的自由變量,那么閉包雖然仍可稱為閉包,但和普通函數(shù)卻沒(méi)有區(qū)別了。例如:

    def f(x):x=3def g(y):return x + yreturn ga = f(1) b = f(3)

    在上面的示例中,x雖然是自由變量,但卻在g()的定義之前就綁定了值(前文介紹過(guò),它稱為bound variable),使得閉包a和閉包b持有的不再是自由變量,而是值對(duì)象完全相同的綁定變量,其值對(duì)象為3,a和b這個(gè)時(shí)候其實(shí)沒(méi)有任何區(qū)別(雖然它們是不同對(duì)象)。換句話說(shuō),有了閉包a就完全沒(méi)有必要再定義另一個(gè)功能上完全相同的閉包b。

    在函數(shù)復(fù)用性的角度上來(lái)說(shuō),這里的a和普通函數(shù)沒(méi)有任何區(qū)別,都只是簡(jiǎn)單地復(fù)用了函數(shù)體。而真正嚴(yán)格意義上的閉包,除了復(fù)用函數(shù)體,還復(fù)用它所在的環(huán)境。

    但是這樣一種情況,對(duì)于通俗定義的閉包來(lái)說(shuō),返回的g()也是一個(gè)閉包,但在嚴(yán)格定義的閉包中,這已經(jīng)不算是閉包。

    再看一個(gè)示例:將自由變量x放在g()函數(shù)定義文本段的后面。

    def f(y):return x+yx=1def g(z):x=3return f(z)print(g(1)) # 輸出2,而不是4

    首先要說(shuō)明的是,python在沒(méi)有給任何作用域修飾符的時(shí)候?qū)崿F(xiàn)的詞法作用域規(guī)則,所以上面return f(z)中的f()看見(jiàn)的是全局變量x(因?yàn)閒()定義在全局文本段中),而不是g()中的x=3。

    回到閉包問(wèn)題上。上面f()持有一個(gè)自由變量x,這個(gè)f(z)的文本定義段是在全局文本段中,它綁定的自由變量x是全局變量(聲明并初始化為空或0),但是這個(gè)變量之后賦值為1了。對(duì)于g()中返回的每個(gè)f()所在的環(huán)境來(lái)說(shuō),它持有的自由變量x一開(kāi)始都是不確定的,但是后來(lái)都確定為1了。這種情況也不能稱之為閉包,因?yàn)?strong>閉包是在f()對(duì)自由變量進(jìn)行綁定時(shí)創(chuàng)建的,而這個(gè)時(shí)候x已經(jīng)是固定的值對(duì)象了。

    回調(diào)函數(shù)、閉包和匿名函數(shù)

    回調(diào)函數(shù)、閉包和匿名函數(shù)其實(shí)沒(méi)有必然的關(guān)系,但因?yàn)楹芏鄷隙紝⒛涿瘮?shù)和回調(diào)函數(shù)、閉包放在一起解釋,讓人誤以為回調(diào)函數(shù)、閉包需要通過(guò)匿名函數(shù)實(shí)現(xiàn)。實(shí)際上,匿名函數(shù)只是一個(gè)有函數(shù)定義文本段,卻沒(méi)有名稱的函數(shù),而閉包則是一個(gè)函數(shù)的實(shí)例加上一個(gè)環(huán)境(嚴(yán)格意義上的定義)。

    對(duì)于閉包和匿名函數(shù)來(lái)說(shuō),仍然以python為例:

    def f(x):def g(y):return x + yreturn g # 返回一個(gè)閉包:有名稱的函數(shù)def h(x):return lambda y: x + y # 返回一個(gè)閉包:匿名函數(shù)# 將執(zhí)行函數(shù)時(shí)返回的閉包函數(shù)賦值給變量 a = f(1) b = h(1)# 調(diào)用存儲(chǔ)在變量中閉包函數(shù) print (a(5)) print (b(5))

    對(duì)于回調(diào)函數(shù)和匿名函數(shù)來(lái)說(shuō),仍然以perl的find函數(shù)為例:

    #!/usr/bin/perl use File::Find;$callback = sub {print "$File::Find::name\n"; }; # 創(chuàng)建一個(gè)匿名函數(shù)以及它的引用find( $callback,"/tmp" ); # 查找/tmp下的文件,每查找到一個(gè)文件,就執(zhí)行一次$callback函數(shù)

    匿名函數(shù)讓閉包的實(shí)現(xiàn)更簡(jiǎn)潔,所以很多時(shí)候返回的閉包函數(shù)就是一個(gè)匿名函數(shù)實(shí)例。

    總結(jié)

    以上是生活随笔為你收集整理的一文搞懂:词法作用域、动态作用域、回调函数、闭包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。