【51CTO獨家特稿】我們在上一篇文章即“循序漸進學Python之函數(shù)入門”中介紹了函數(shù)的定義和調(diào)用方法,那里定義的函數(shù)都是相互平行的,也就是說,所有函數(shù)都是在其它函數(shù)之外定義的。而本文介紹的是函數(shù)的嵌套:即函數(shù)內(nèi)部的函數(shù)。我們這里首先介紹了嵌套函數(shù)的定義,以及嵌套函數(shù)中變量的查找過程,然后后講解多層嵌套函數(shù)的執(zhí)行過程,最后說明了嵌套作用域的靜態(tài)性。 一、函數(shù)的嵌套定義 學習過C語言的讀者都知道,C語言的函數(shù)定義都是相互獨立的,也就是說,在定義函數(shù)的時候,不能在這個函數(shù)內(nèi)部包含其它函數(shù)的定義。但是Python語言正好與之相反,它允許在定義函數(shù)的時候,其函數(shù)體內(nèi)又包含另外一個函數(shù)的完整定義,這就是我們通常所說的嵌套定義。為什么?因為函數(shù)是用def語句定義的,凡是其他語句可以出現(xiàn)的地方,def語句同樣可以出現(xiàn)。 下面是一個內(nèi)部函數(shù)的例子:
上面的定義在交互方式下的運行情況如下所示: 二、嵌套函數(shù)的作用域 內(nèi)部函數(shù)定義的變量只在內(nèi)部有效,包括其嵌套的內(nèi)部函數(shù),但是對外部函數(shù)無效,如下例所示:
對于上面的代碼,我們定義了三個函數(shù),其中函數(shù)f()是函數(shù)f1()的外部函數(shù),而函數(shù)f1()又是函數(shù)f2()的外部函數(shù)。在這三個函數(shù)中我們僅定義了一個變量x,并且該變量是在函數(shù)f1()中定義的,然后我們分別在函數(shù)f1()和它的內(nèi)部函數(shù)f2()中打印該變量的值。在交互式環(huán)境下的執(zhí)行情況如下所示:
上圖說明內(nèi)部函數(shù)可以引用外部函數(shù)中定義的變量,但是外部函數(shù)卻不能引用內(nèi)部函數(shù)定義的變量,例如:
在上面的示例代碼中,我們在外部函數(shù)f()中定義了一個變量x,然后又在內(nèi)部函數(shù)f1()中定義了一個變量y;然后分別在內(nèi)部函數(shù)和外部函數(shù)中引用這兩個變量,代碼的交互式執(zhí)行情況如下所示:
很明顯,程序在引用內(nèi)部變量的時候出錯了,因為內(nèi)部函數(shù)定義的變量只對這個函數(shù)本身及其內(nèi)部函數(shù)可見,而不對其外部函數(shù)可見,所以在外部函數(shù)f()看來,變量y尚未定義。 三、變量的四種作用域 介紹了嵌套的內(nèi)部函數(shù)之后,我們已經(jīng)接觸到了Python中變量的四種作用域,它們分別是局部作用域、全局作用域、外部作用域和Python內(nèi)建的作用域。其中,局部作用域?qū)诤瘮?shù)本身,外部作用域?qū)谕獠亢瘮?shù)(如果有的話),全局作用域?qū)谀K(或文件),Python內(nèi)建的作用域?qū)赑ython解釋程序。這四種作用域的包含關(guān)系如下所示: 四、Python內(nèi)建的作用域 通過閱讀本系列的文章,相信讀者對于局部作用域、全局作用域和外部作用域已經(jīng)有所了解了,現(xiàn)在我們再來介紹一下有效范圍最大,即對應于Python解釋程序范圍的Python內(nèi)建的作用域。 實際上,Python解釋程序有一個預建的,或者叫自帶的模塊,叫做__builtin__。我們可以在Python解釋程序中導入該模塊,并查看其中預定義的名稱。如下圖所示: 上圖為我們展示了Python內(nèi)建的作用域中已定義的所有名稱,其中大部分一些是變量名,一些是函數(shù)名。如果有興趣的話,可以直接在提示符下輸入這些名稱,來引用它們,如: 當我們在提示符下面輸入上面列出的函數(shù)名時,系統(tǒng)提示這些是內(nèi)建的函數(shù)名。 五、變量名的查找順序 上面說過,Python中的變量有四種作用域,那么當函數(shù)引用一個變量時,它是以怎樣的順序在這些作用域中查找變量呢?下面我們將詳細說明。 當某個函數(shù)引用一個變量時,比如變量x,它首先在該函數(shù)的局部作用域中查找該變量,如果在局部作用域中有對該變量的賦值語句,并且沒有用關(guān)鍵詞global,即沒有將其聲明為全局變量,如下所示: x = 6 這相當于在該函數(shù)內(nèi)部定義了一個局部變量,那么這個名為x值為6的整型變量就是我們要找的。如果在當前函數(shù)的局部作用域中沒有找到該變量的定義,并且當前函數(shù)是某個函數(shù)的內(nèi)部函數(shù)的話,那么繼續(xù)由內(nèi)向外在所有外部函數(shù)中查找該變量的定義,并且將最先找到的賦值語句作為它的定義,并將第一個賦給它的值作為我們要找的變量的值。如果在所有外部函數(shù)中都沒有找到該變量的定義,或者根本就沒有外部函數(shù),那么繼續(xù)在全局作用域中查找。如果在全局作用域中還沒有找到x的定義,那么就到Python內(nèi)建的作用域去找,如果四個作用域都沒找到的話,則說明引用了一個未加定義的變量,這時Python解釋器就會報錯。 讀者可以對照圖4進行理解。 下面我們以示例代碼進行講解,代碼如下所示:
上述代碼中出現(xiàn)了多層嵌套的函數(shù),其中函數(shù)f3()嵌套在函數(shù)f2()中,函數(shù)f2()嵌套在函數(shù)f1()中,函數(shù)f1()嵌套在函數(shù)f()中。但是,每個函數(shù)中都對變量x進行了定義,所以各個函數(shù)都能在其局部定義域中找到變量x。上述代碼在交互式環(huán)境下的執(zhí)行結(jié)果如下所示: 在函數(shù)f()中,變量x被賦值為0,根據(jù)查找變量時先從當前函數(shù)的局部作用域下手的規(guī)則,函數(shù)f()看到的變量x的值就是0,其它函數(shù)依此類推。 現(xiàn)在我們對上述代碼稍作修改,將函數(shù)f3()和函數(shù)f2()中對變量x的定義去掉,看一下在當前函數(shù)中找不到變量的定義時的情形,代碼如下所示:
上述修改后的代碼在交互環(huán)境下執(zhí)行情況如下所示: 從圖8可以看出,在函數(shù)f3()、f2()、f1()中,變量的值都為1;只有在f0()中變量的值為0。對于函數(shù)f3()來說,當它打印變量x的值時,首先在其局部作用域中尋找變量x的定義,因為沒有找到,所以繼續(xù)向外到其外部函數(shù)f2()中尋找,結(jié)果在函數(shù)f2()的局部變量中也沒有找到變量x的定義,所以又向函數(shù)f2()的外部函數(shù)即函數(shù)f1()中尋找,這次找到了變量x的定義,即
所以函數(shù)f3()打印的變量x的值為1。當函數(shù)f2()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,沒找到,所以繼續(xù)向外部函數(shù)f1()中尋找,這次找到了變量x的定義,即
所以函數(shù)f2()打印的變量x的值為1。當函數(shù)f1()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,在其局部作用域中找到了變量x的定義,其值為1,所以直接打印變量x的值。同理,當函數(shù)f()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,在其局部作用域中找到了變量x的定義,其值為0,所以直接打印變量x的值。 需要注意的是,如果內(nèi)部函數(shù)將變量聲明為全局變量,那么Python就會跳過局部作用域和外部作用域,而直接從全局作用域開始尋找該變量的定義。現(xiàn)在舉例說明:
上面的代碼以交互式執(zhí)行,結(jié)果如下所示: 從圖10可以看出,當函數(shù)f1()執(zhí)行后,其外部函數(shù)f()的局部變量并沒有發(fā)生變化,但是全局變量x的值卻變了。這是因為,當在內(nèi)部函數(shù)f1()中將變量x聲明為全局變量后,賦值語句
的作用并不是在當前函數(shù)中定義一個局部變量,因為這時函數(shù)f1()會直接到全局作用域中查找變量,所以賦值語句實際的作用是修改了全局變量x的值。 六、函數(shù)嵌套時的執(zhí)行順序 當初學者遇到多層嵌套的函數(shù)時,對于各個函數(shù)的執(zhí)行順序經(jīng)常感到非常頭疼,不過不要緊,我們這里向大家介紹一種非常簡單的辦法,讓您輕松讀懂代碼的執(zhí)行順序。 我們曾經(jīng)說過,在使用def語句定義函數(shù)時,Python遇到該語句并不會立即執(zhí)行其語句體中的代碼,只有遇到該函數(shù)的調(diào)用時,對應def語句的語句體才會被執(zhí)行。所以,當我們分析嵌套函數(shù)的執(zhí)行順序時,遇到def語句可以先行跳過(包括其語句體),然后遇到函數(shù)調(diào)用時,在返回頭來查看對應def語句的語句體。我們以下列代碼進行說明:
上述代碼在交互式環(huán)境下的執(zhí)行情況如下所示: 現(xiàn)在分析一下上述代碼的執(zhí)行過程。首先,當我們在命令提示符下調(diào)用函數(shù)f()時,解釋器會尋找到該函數(shù)的定義。在函數(shù)f()的定義的第一行下面,雖然這里的代碼很多,但是我們只關(guān)心冒號下面的第一次縮進所涉及的那些語句,在這里有四個語句,一個賦值語句,一個打印語句,一個def語句,一個函數(shù)調(diào)用語句。注意,這里的def語句及其語句體可以先跳過去。所以,除了def語句外,這四條語句的執(zhí)行順序基本上是順序執(zhí)行的,即先給變量x賦值,令其為整數(shù)0,再輸出變量x的值,這時系統(tǒng)將輸出:
然后調(diào)用f1(),最后找到定義f1()的def語句,并執(zhí)行其語句體。我們看到,在定義f1()的def語句下面的第一次縮進對應有四條語句,分別也是一個賦值語句,一個打印語句,一個def語句,一個函數(shù)調(diào)用語句。執(zhí)行時,先賦值,即將變量x賦值為1,再打印,系統(tǒng)輸出以下內(nèi)容:
遇到def語句先跳過,繼而執(zhí)行函數(shù)調(diào)用,這次調(diào)用的是函數(shù)f2()。我們找到定義該函數(shù)的def語句,并執(zhí)行該語句第一行下面第一次縮進對應的四條語句。首先將變量x賦值為2,再打印,系統(tǒng)輸出以下內(nèi)容:
接著,先跳過def語句,直接執(zhí)行函數(shù)調(diào)用,這次調(diào)用的是函數(shù)f3()。我們找到定義該函數(shù)的def語句,并執(zhí)行其第一行下面第一次縮進對應的兩條語句。首先將變量x賦值為3,再打印變量的值,如下所示:
好了,至此這個多層嵌套的函數(shù)的執(zhí)行過程至此分析完畢。作為一個練習,讀者可以按照上面介紹的方法分析一下下列代碼,然后在交互環(huán)境中執(zhí)行一下,看看分析的對不對:
七、嵌套作用域的靜態(tài)性 前面說過,函數(shù)內(nèi)定義的變量局部只有在函數(shù)執(zhí)行時有效,當函數(shù)退出后就不能再訪問了,例如下列代碼:
當我們在交互方式下執(zhí)行上述代碼時,結(jié)果如下: 當我們在函數(shù)中引用局部變量時,完全合法。但是,一旦函數(shù)退出,再次引用函數(shù)內(nèi)的局部變量就會出錯。這是因為局部變量是動態(tài)分配的,當函數(shù)執(zhí)行時為其分配臨時內(nèi)存,函數(shù)執(zhí)行后馬上釋放。這反映出局部變量的動態(tài)性。但是當出現(xiàn)函數(shù)嵌套時,情況會有所變化。請看下列代碼:
我們在交互環(huán)境下執(zhí)行上述代碼,如: 我們說明一下上面的代碼。首先,我們在函數(shù)f()中定義了一個局部變量x和一個內(nèi)部函數(shù)f1(),最后將內(nèi)部函數(shù)名作為返回值。注意,我們在內(nèi)部函數(shù)f1()中引用了外部函數(shù)f()的局部變量。我們在命令提示符下調(diào)用函數(shù)f(),并將返回值賦給printx,這時變量printx中實際上存放的是內(nèi)部函數(shù)f1()的函數(shù)名f1。所以在命令行中輸入printx()實際上就是在調(diào)用內(nèi)部函數(shù)f1()。然后內(nèi)部函數(shù)執(zhí)行,并引用外部函數(shù)f()的局部變量x。這說明,發(fā)生函數(shù)嵌套后,如果內(nèi)部函數(shù)引用了外部函數(shù)的局部變量,那么外部函數(shù)的局部變量將被靜態(tài)存儲,即當函數(shù)退出后,其局部變量所占內(nèi)存也不會被釋放。 八、小結(jié) 在本文中,我們?yōu)樽x者介紹了函數(shù)的嵌套。除了嵌套函數(shù)的定義外,我們詳細解釋了嵌套函數(shù)中變量的查找過程以及相關(guān)的四種作用域,然后后講解多層嵌套函數(shù)的執(zhí)行過程,最后說明了嵌套作用域的靜態(tài)性。為了幫助讀者理解本文內(nèi)容,我們給出了大量示例代碼,讀者可以利用這些代碼實際運行、分析,從而加深理解。 【相關(guān)文章】 【責任編輯:red7 TEL:(010)68476606】 |
|