python嵌套函数的执行顺序由外到内_循序渐进学Python之函数的嵌套

【51CTO独家特稿】我们在上一篇文章即“循序渐进学Python之函数入门”中介绍了函数的定义和调用方法,那里定义的函数都是相互平行的,也就是说,所有函数都是在其它函数之外定义的。而本文介绍的是函数的嵌套:即函数内部的函数。我们这里首先介绍了嵌套函数的定义,以及嵌套函数中变量的查找过程,然后后讲解多层嵌套函数的执行过程,最后说明了嵌套作用域的静态性。

一、函数的嵌套定义

学习过C语言的读者都知道,C语言的函数定义都是相互独立的,也就是说,在定义函数的时候,不能在这个函数内部包含其它函数的定义。但是Python语言正好与之相反,它允许在定义函数的时候,其函数体内又包含另外一个函数的完整定义,这就是我们通常所说的嵌套定义。为什么?因为函数是用def语句定义的,凡是其他语句可以出现的地方,def语句同样可以出现。

像这样定义在其他函数内的函数叫做内部函数,内部函数所在的函数叫做外部函数。当然,我们可以多层嵌套,这样的话,除了最外层和最内层的函数之外,其它函数既是外部函数又是内部函数。

下面是一个内部函数的例子:

def f():

char='hello'

def f1():

print char

f1()

上面的定义在交互方式下的运行情况如下所示:

图1  函数的嵌套定义

二、嵌套函数的作用域

内部函数定义的变量只在内部有效,包括其嵌套的内部函数,但是对外部函数无效,如下例所示:

def f():

def f1():

x=3

print '''目前在函数f1()中:x=''', x

def f2():

print '''目前在函数f2()中:x=''', x

f2()

f1()

对于上面的代码,我们定义了三个函数,其中函数f()是函数f1()的外部函数,而函数f1()又是函数f2()的外部函数。在这三个函数中我们仅定义了一个变量x,并且该变量是在函数f1()中定义的,然后我们分别在函数f1()和它的内部函数f2()中打印该变量的值。在交互式环境下的执行情况如下所示:

图2  内部函数可以使用外部函数的变量

上图说明内部函数可以引用外部函数中定义的变量,但是外部函数却不能引用内部函数定义的变量,例如:

def f():

x=6

def f1():

y=18

print '''目前在内部函数f1()中:'''

print 'x=',x,'y=',y

f1()

print '''目前在外部函数f2()中:'''

print 'x=',x,'y=',y

在上面的示例代码中,我们在外部函数f()中定义了一个变量x,然后又在内部函数f1()中定义了一个变量y;然后分别在内部函数和外部函数中引用这两个变量,代码的交互式执行情况如下所示:

图3  外部函数不可以使用内部函数的变量

很明显,程序在引用内部变量的时候出错了,因为内部函数定义的变量只对这个函数本身及其内部函数可见,而不对其外部函数可见,所以在外部函数f()看来,变量y尚未定义。

三、变量的四种作用域

介绍了嵌套的内部函数之后,我们已经接触到了Python中变量的四种作用域,它们分别是局部作用域、全局作用域、外部作用域和Python内建的作用域。其中,局部作用域对应于函数本身,外部作用域对应于外部函数(如果有的话),全局作用域对应于模块(或文件),Python内建的作用域对应于Python解释程序。这四种作用域的包含关系如下所示:

图4  变量的四种作用域

四、Python内建的作用域

通过阅读本系列的文章,相信读者对于局部作用域、全局作用域和外部作用域已经有所了解了,现在我们再来介绍一下有效范围最大,即对应于Python解释程序范围的Python内建的作用域。

实际上,Python解释程序有一个预建的,或者叫自带的模块,叫做__builtin__。我们可以在Python解释程序中导入该模块,并查看其中预定义的名称。如下图所示:

图5  查看__builtin__模块中预定义的名称

上图为我们展示了Python内建的作用域中已定义的所有名称,其中大部分一些是变量名,一些是函数名。如果有兴趣的话,可以直接在提示符下输入这些名称,来引用它们,如:

图6  引用内建的函数名

当我们在提示符下面输入上面列出的函数名时,系统提示这些是内建的函数名。

五、变量名的查找顺序

上面说过,Python中的变量有四种作用域,那么当函数引用一个变量时,它是以怎样的顺序在这些作用域中查找变量呢?下面我们将详细说明。

当某个函数引用一个变量时,比如变量x,它首先在该函数的局部作用域中查找该变量,如果在局部作用域中有对该变量的赋值语句,并且没有用关键词global,即没有将其声明为全局变量,如下所示:

x = 6

这相当于在该函数内部定义了一个局部变量,那么这个名为x值为6的整型变量就是我们要找的。如果在当前函数的局部作用域中没有找到该变量的定义,并且当前函数是某个函数的内部函数的话,那么继续由内向外在所有外部函数中查找该变量的定义,并且将最先找到的赋值语句作为它的定义,并将第一个赋给它的值作为我们要找的变量的值。如果在所有外部函数中都没有找到该变量的定义,或者根本就没有外部函数,那么继续在全局作用域中查找。如果在全局作用域中还没有找到x的定义,那么就到Python内建的作用域去找,如果四个作用域都没找到的话,则说明引用了一个未加定义的变量,这时Python解释器就会报错。

读者可以对照图4进行理解。

下面我们以示例代码进行讲解,代码如下所示:

def f():

x = 0

def f1():

x = 1

def f2():

x = 2

def f3():

x = 3

print '目前在函数f3()中,变量x的值为:',x

print '目前在函数f2()中,变量x的值为:',x

f3()

print '目前在函数f1()中,变量x的值为:',x

f2()

print '目前在函数f()中,变量x的值为:',x

f1()

上述代码中出现了多层嵌套的函数,其中函数f3()嵌套在函数f2()中,函数f2()嵌套在函数f1()中,函数f1()嵌套在函数f()中。但是,每个函数中都对变量x进行了定义,所以各个函数都能在其局部定义域中找到变量x。上述代码在交互式环境下的执行结果如下所示:

图7  在局部作用域中找到变量x的情形

在函数f()中,变量x被赋值为0,根据查找变量时先从当前函数的局部作用域下手的规则,函数f()看到的变量x的值就是0,其它函数依此类推。

现在我们对上述代码稍作修改,将函数f3()和函数f2()中对变量x的定义去掉,看一下在当前函数中找不到变量的定义时的情形,代码如下所示:

def f():

x = 0

def f1():

x = 1

def f2():

def f3():

print '目前在函数f3()中,变量x的值为:',x

print '目前在函数f2()中,变量x的值为:',x

f3()

print '目前在函数f1()中,变量x的值为:',x

f2()

print '目前在函数f()中,变量x的值为:',x

f1()

上述修改后的代码在交互环境下执行情况如下所示:

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

从图8可以看出,在函数f3()、f2()、f1()中,变量的值都为1;只有在f0()中变量的值为0。对于函数f3()来说,当它打印变量x的值时,首先在其局部作用域中寻找变量x的定义,因为没有找到,所以继续向外到其外部函数f2()中寻找,结果在函数f2()的局部变量中也没有找到变量x的定义,所以又向函数f2()的外部函数即函数f1()中寻找,这次找到了变量x的定义,即

x = 1

所以函数f3()打印的变量x的值为1。当函数f2()执行时,首先在其局部作用域中寻找变量x的定义,没找到,所以继续向外部函数f1()中寻找,这次找到了变量x的定义,即

x = 1

所以函数f2()打印的变量x的值为1。当函数f1()执行时,首先在其局部作用域中寻找变量x的定义,在其局部作用域中找到了变量x的定义,其值为1,所以直接打印变量x的值。同理,当函数f()执行时,首先在其局部作用域中寻找变量x的定义,在其局部作用域中找到了变量x的定义,其值为0,所以直接打印变量x的值。

需要注意的是,如果内部函数将变量声明为全局变量,那么Python就会跳过局部作用域和外部作用域,而直接从全局作用域开始寻找该变量的定义。现在举例说明:

x = 8 #定义一个全局变量

def f():

x = 0 #定义一个局部变量

print '在函数f()中,x=',x

def f1():

global x #将x声明为全局变量

x = 1 #这个赋值语句并没有定义局部变量,而是修改了全局变量的值

print '在函数f1()中,x=',x

f1()

print '现在函数f()中,x=',x

上面的代码以交互式执行,结果如下所示:

图9  在内部函数中使用全局变量的情形

从图10可以看出,当函数f1()执行后,其外部函数f()的局部变量并没有发生变化,但是全局变量x的值却变了。这是因为,当在内部函数f1()中将变量x声明为全局变量后,赋值语句

x = 1

的作用并不是在当前函数中定义一个局部变量,因为这时函数f1()会直接到全局作用域中查找变量,所以赋值语句实际的作用是修改了全局变量x的值。

六、函数嵌套时的执行顺序

当初学者遇到多层嵌套的函数时,对于各个函数的执行顺序经常感到非常头疼,不过不要紧,我们这里向大家介绍一种非常简单的办法,让您轻松读懂代码的执行顺序。

我们曾经说过,在使用def语句定义函数时,Python遇到该语句并不会立即执行其语句体中的代码,只有遇到该函数的调用时,对应def语句的语句体才会被执行。所以,当我们分析嵌套函数的执行顺序时,遇到def语句可以先行跳过(包括其语句体),然后遇到函数调用时,在返回头来查看对应def语句的语句体。我们以下列代码进行说明:

def f():

x = 0

print '当前正在执行函数f(),其变量x的值为:',x

def f1():

x = 1

print '当前正在执行函数f1()中,其变量x的值为:',x

def f2():

x = 2

print '当前正在执行函数f2()中,其变量x的值为:',x

def f3():

x = 3

print '当前正在执行函数f3()中,其变量x的值为:',x

f3()

f2()

f1()

上述代码在交互式环境下的执行情况如下所示:

图10  多层嵌套函数执行顺序示例1

现在分析一下上述代码的执行过程。首先,当我们在命令提示符下调用函数f()时,解释器会寻找到该函数的定义。在函数f()的定义的第一行下面,虽然这里的代码很多,但是我们只关心冒号下面的第一次缩进所涉及的那些语句,在这里有四个语句,一个赋值语句,一个打印语句,一个def语句,一个函数调用语句。注意,这里的def语句及其语句体可以先跳过去。所以,除了def语句外,这四条语句的执行顺序基本上是顺序执行的,即先给变量x赋值,令其为整数0,再输出变量x的值,这时系统将输出:

当前正在执行函数f(),其变量x的值为: 0

然后调用f1(),最后找到定义f1()的def语句,并执行其语句体。我们看到,在定义f1()的def语句下面的第一次缩进对应有四条语句,分别也是一个赋值语句,一个打印语句,一个def语句,一个函数调用语句。执行时,先赋值,即将变量x赋值为1,再打印,系统输出以下内容:

当前正在执行函数f1()中,其变量x的值为: 1

遇到def语句先跳过,继而执行函数调用,这次调用的是函数f2()。我们找到定义该函数的def语句,并执行该语句第一行下面第一次缩进对应的四条语句。首先将变量x赋值为2,再打印,系统输出以下内容:

当前正在执行函数f2()中,其变量x的值为: 2

接着,先跳过def语句,直接执行函数调用,这次调用的是函数f3()。我们找到定义该函数的def语句,并执行其第一行下面第一次缩进对应的两条语句。首先将变量x赋值为3,再打印变量的值,如下所示:

当前正在执行函数f3()中,其变量x的值为: 3

好了,至此这个多层嵌套的函数的执行过程至此分析完毕。作为一个练习,读者可以按照上面介绍的方法分析一下下列代码,然后在交互环境中执行一下,看看分析的对不对:

def f():

x = 0

def f1():

x = 1

def f2():

x = 2

def f3():

x = 3

print '目前在函数f3()中,变量x的值为:',x

f3()

print '目前在函数f2()中,变量x的值为:',x

f2()

print '目前在函数f1()中,变量x的值为:',x

f1()

print '目前在函数f()中,变量x的值为:',x

七、嵌套作用域的静态性

前面说过,函数内定义的变量局部只有在函数执行时有效,当函数退出后就不能再访问了,例如下列代码:

def f():

x = 6

print x

当我们在交互方式下执行上述代码时,结果如下:

图11  局部变量的动态性

当我们在函数中引用局部变量时,完全合法。但是,一旦函数退出,再次引用函数内的局部变量就会出错。这是因为局部变量是动态分配的,当函数执行时为其分配临时内存,函数执行后马上释放。这反映出局部变量的动态性。但是当出现函数嵌套时,情况会有所变化。请看下列代码:

def f( ):

x = 8

def f1( ):

print x

return f1

我们在交互环境下执行上述代码,如:

图12  嵌套作用域的动态性

我们说明一下上面的代码。首先,我们在函数f()中定义了一个局部变量x和一个内部函数f1(),最后将内部函数名作为返回值。注意,我们在内部函数f1()中引用了外部函数f()的局部变量。我们在命令提示符下调用函数f(),并将返回值赋给printx,这时变量printx中实际上存放的是内部函数f1()的函数名f1。所以在命令行中输入printx()实际上就是在调用内部函数f1()。然后内部函数执行,并引用外部函数f()的局部变量x。这说明,发生函数嵌套后,如果内部函数引用了外部函数的局部变量,那么外部函数的局部变量将被静态存储,即当函数退出后,其局部变量所占内存也不会被释放。

八、小结

在本文中,我们为读者介绍了函数的嵌套。除了嵌套函数的定义外,我们详细解释了嵌套函数中变量的查找过程以及相关的四种作用域,然后后讲解多层嵌套函数的执行过程,最后说明了嵌套作用域的静态性。为了帮助读者理解本文内容,我们给出了大量示例代码,读者可以利用这些代码实际运行、分析,从而加深理解。

【相关文章】

【责任编辑:red7 TEL:(010)68476606】

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页
评论

打赏作者

张野野

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值