日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

循序漸進學Python之函數(shù)的嵌套

 sven_ 2014-01-16

【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)。
像這樣定義在其他函數(shù)內(nèi)的函數(shù)叫做內(nèi)部函數(shù),內(nèi)部函數(shù)所在的函數(shù)叫做外部函數(shù)。當然,我們可以多層嵌套,這樣的話,除了最外層和最內(nèi)層的函數(shù)之外,其它函數(shù)既是外部函數(shù)又是內(nèi)部函數(shù)。

下面是一個內(nèi)部函數(shù)的例子:

def f():
char='hello'
def f1():
print char
f1()

上面的定義在交互方式下的運行情況如下所示:


圖1  函數(shù)的嵌套定義

二、嵌套函數(shù)的作用域

內(nèi)部函數(shù)定義的變量只在內(nèi)部有效,包括其嵌套的內(nèi)部函數(shù),但是對外部函數(shù)無效,如下例所示:

def f():
def f1():
x=3
print '''目前在函數(shù)f1()中:x=''', x
def f2():
print '''目前在函數(shù)f2()中:x=''', x
f2()
f1()

對于上面的代碼,我們定義了三個函數(shù),其中函數(shù)f()是函數(shù)f1()的外部函數(shù),而函數(shù)f1()又是函數(shù)f2()的外部函數(shù)。在這三個函數(shù)中我們僅定義了一個變量x,并且該變量是在函數(shù)f1()中定義的,然后我們分別在函數(shù)f1()和它的內(nèi)部函數(shù)f2()中打印該變量的值。在交互式環(huán)境下的執(zhí)行情況如下所示:


 圖2  內(nèi)部函數(shù)可以使用外部函數(shù)的變量

上圖說明內(nèi)部函數(shù)可以引用外部函數(shù)中定義的變量,但是外部函數(shù)卻不能引用內(nèi)部函數(shù)定義的變量,例如:

def f():
x=6
def f1():
y=18
print '''目前在內(nèi)部函數(shù)f1()中:'''
print 'x=',x,'y=',y
f1()
print '''目前在外部函數(shù)f2()中:'''
print 'x=',x,'y=',y

在上面的示例代碼中,我們在外部函數(shù)f()中定義了一個變量x,然后又在內(nèi)部函數(shù)f1()中定義了一個變量y;然后分別在內(nèi)部函數(shù)和外部函數(shù)中引用這兩個變量,代碼的交互式執(zhí)行情況如下所示:


圖3  外部函數(shù)不可以使用內(nèi)部函數(shù)的變量

很明顯,程序在引用內(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)系如下所示:


圖4  變量的四種作用域

四、Python內(nèi)建的作用域

通過閱讀本系列的文章,相信讀者對于局部作用域、全局作用域和外部作用域已經(jīng)有所了解了,現(xiàn)在我們再來介紹一下有效范圍最大,即對應于Python解釋程序范圍的Python內(nèi)建的作用域。

實際上,Python解釋程序有一個預建的,或者叫自帶的模塊,叫做__builtin__。我們可以在Python解釋程序中導入該模塊,并查看其中預定義的名稱。如下圖所示:


圖5  查看__builtin__模塊中預定義的名稱

上圖為我們展示了Python內(nèi)建的作用域中已定義的所有名稱,其中大部分一些是變量名,一些是函數(shù)名。如果有興趣的話,可以直接在提示符下輸入這些名稱,來引用它們,如:


圖6  引用內(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進行理解。

下面我們以示例代碼進行講解,代碼如下所示:

def f():
x = 0
def f1():
x = 1
def f2():
x = 2
def f3():
x = 3
print '目前在函數(shù)f3()中,變量x的值為:',x
print '目前在函數(shù)f2()中,變量x的值為:',x
f3()
print '目前在函數(shù)f1()中,變量x的值為:',x
f2()
print '目前在函數(shù)f()中,變量x的值為:',x
f1()

上述代碼中出現(xiàn)了多層嵌套的函數(shù),其中函數(shù)f3()嵌套在函數(shù)f2()中,函數(shù)f2()嵌套在函數(shù)f1()中,函數(shù)f1()嵌套在函數(shù)f()中。但是,每個函數(shù)中都對變量x進行了定義,所以各個函數(shù)都能在其局部定義域中找到變量x。上述代碼在交互式環(huán)境下的執(zhí)行結(jié)果如下所示:


圖7  在局部作用域中找到變量x的情形

在函數(shù)f()中,變量x被賦值為0,根據(jù)查找變量時先從當前函數(shù)的局部作用域下手的規(guī)則,函數(shù)f()看到的變量x的值就是0,其它函數(shù)依此類推。

現(xiàn)在我們對上述代碼稍作修改,將函數(shù)f3()和函數(shù)f2()中對變量x的定義去掉,看一下在當前函數(shù)中找不到變量的定義時的情形,代碼如下所示:

def f():
x = 0
def f1():
x = 1
def f2():
def f3():
print '目前在函數(shù)f3()中,變量x的值為:',x
print '目前在函數(shù)f2()中,變量x的值為:',x
f3()
print '目前在函數(shù)f1()中,變量x的值為:',x
f2()
print '目前在函數(shù)f()中,變量x的值為:',x
f1()

上述修改后的代碼在交互環(huán)境下執(zhí)行情況如下所示:


圖8   在局部作用域中找不到變量x的情形

從圖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的定義,即

x = 1

所以函數(shù)f3()打印的變量x的值為1。當函數(shù)f2()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,沒找到,所以繼續(xù)向外部函數(shù)f1()中尋找,這次找到了變量x的定義,即

x = 1

所以函數(shù)f2()打印的變量x的值為1。當函數(shù)f1()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,在其局部作用域中找到了變量x的定義,其值為1,所以直接打印變量x的值。同理,當函數(shù)f()執(zhí)行時,首先在其局部作用域中尋找變量x的定義,在其局部作用域中找到了變量x的定義,其值為0,所以直接打印變量x的值。

需要注意的是,如果內(nèi)部函數(shù)將變量聲明為全局變量,那么Python就會跳過局部作用域和外部作用域,而直接從全局作用域開始尋找該變量的定義。現(xiàn)在舉例說明:

x = 8      #定義一個全局變量
def f():
x = 0      #定義一個局部變量
print '在函數(shù)f()中,x=',x
def f1():
global x      #將x聲明為全局變量
x = 1      #這個賦值語句并沒有定義局部變量,而是修改了全局變量的值
print '在函數(shù)f1()中,x=',x
f1()
print '現(xiàn)在函數(shù)f()中,x=',x

上面的代碼以交互式執(zhí)行,結(jié)果如下所示:


 圖9  在內(nèi)部函數(shù)中使用全局變量的情形

從圖10可以看出,當函數(shù)f1()執(zhí)行后,其外部函數(shù)f()的局部變量并沒有發(fā)生變化,但是全局變量x的值卻變了。這是因為,當在內(nèi)部函數(shù)f1()中將變量x聲明為全局變量后,賦值語句

x = 1

的作用并不是在當前函數(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語句的語句體。我們以下列代碼進行說明:

def f():
x = 0
print '當前正在執(zhí)行函數(shù)f(),其變量x的值為:',x
def f1():
x = 1
print '當前正在執(zhí)行函數(shù)f1()中,其變量x的值為:',x
def f2():
x = 2
print '當前正在執(zhí)行函數(shù)f2()中,其變量x的值為:',x
def f3():
x = 3
print '當前正在執(zhí)行函數(shù)f3()中,其變量x的值為:',x
f3()
f2()
f1()

上述代碼在交互式環(huán)境下的執(zhí)行情況如下所示:


圖10  多層嵌套函數(shù)執(zhí)行順序示例1

現(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)將輸出:

當前正在執(zhí)行函數(shù)f(),其變量x的值為: 0

然后調(diào)用f1(),最后找到定義f1()的def語句,并執(zhí)行其語句體。我們看到,在定義f1()的def語句下面的第一次縮進對應有四條語句,分別也是一個賦值語句,一個打印語句,一個def語句,一個函數(shù)調(diào)用語句。執(zhí)行時,先賦值,即將變量x賦值為1,再打印,系統(tǒng)輸出以下內(nèi)容:

當前正在執(zhí)行函數(shù)f1()中,其變量x的值為: 1

遇到def語句先跳過,繼而執(zhí)行函數(shù)調(diào)用,這次調(diào)用的是函數(shù)f2()。我們找到定義該函數(shù)的def語句,并執(zhí)行該語句第一行下面第一次縮進對應的四條語句。首先將變量x賦值為2,再打印,系統(tǒng)輸出以下內(nèi)容:

當前正在執(zhí)行函數(shù)f2()中,其變量x的值為: 2

接著,先跳過def語句,直接執(zhí)行函數(shù)調(diào)用,這次調(diào)用的是函數(shù)f3()。我們找到定義該函數(shù)的def語句,并執(zhí)行其第一行下面第一次縮進對應的兩條語句。首先將變量x賦值為3,再打印變量的值,如下所示:

當前正在執(zhí)行函數(shù)f3()中,其變量x的值為: 3

好了,至此這個多層嵌套的函數(shù)的執(zhí)行過程至此分析完畢。作為一個練習,讀者可以按照上面介紹的方法分析一下下列代碼,然后在交互環(huán)境中執(zhí)行一下,看看分析的對不對:

def f():
x = 0
def f1():
x = 1
def f2():
x = 2
def f3():
x = 3
print '目前在函數(shù)f3()中,變量x的值為:',x
f3()
print '目前在函數(shù)f2()中,變量x的值為:',x
f2()
print '目前在函數(shù)f1()中,變量x的值為:',x
f1()
print '目前在函數(shù)f()中,變量x的值為:',x

七、嵌套作用域的靜態(tài)性

前面說過,函數(shù)內(nèi)定義的變量局部只有在函數(shù)執(zhí)行時有效,當函數(shù)退出后就不能再訪問了,例如下列代碼:

 def f():
x = 6
print x

當我們在交互方式下執(zhí)行上述代碼時,結(jié)果如下:


圖11  局部變量的動態(tài)性

當我們在函數(shù)中引用局部變量時,完全合法。但是,一旦函數(shù)退出,再次引用函數(shù)內(nèi)的局部變量就會出錯。這是因為局部變量是動態(tài)分配的,當函數(shù)執(zhí)行時為其分配臨時內(nèi)存,函數(shù)執(zhí)行后馬上釋放。這反映出局部變量的動態(tài)性。但是當出現(xiàn)函數(shù)嵌套時,情況會有所變化。請看下列代碼:

def f(  ):
    x = 8
    def f1(  ):
        print x
    return f1

我們在交互環(huán)境下執(zhí)行上述代碼,如:


圖12  嵌套作用域的動態(tài)性

我們說明一下上面的代碼。首先,我們在函數(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】

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多