打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
循序渐进学Python之函数的嵌套
【51CTO独家特稿】我们在上一篇文章即“循序渐进学Python之函数入门”中介绍了函数的定义和调用方法,那里定义的函数都是相互平行的,也就是说,所有函数都是在其它函数之外定义的。而本文介绍的是函数的嵌套:即函数内部的函数。我们这里首先介绍了嵌套函数的定义,以及嵌套函数中变量的查找过程,然后后讲解多层嵌套函数的执行过程,最后说明了嵌套作用域的静态性。
一、函数的嵌套定义
学习过C语言的读者都知道,C语言的函数定义都是相互独立的,也就是说,在定义函数的时候,不能在这个函数内部包含其它函数的定义。但是Python语言正好与之相反,它允许在定义函数的时候,其函数体内又包含另外一个函数的完整定义,这就是我们通常所说的嵌套定义。为什么?因为函数是用def语句定义的,凡是其他语句可以出现的地方,def语句同样可以出现。
像这样定义在其他函数内的函数叫做内部函数,内部函数所在的函数叫做外部函数。当然,我们可以多层嵌套,这样的话,除了最外层和最内层的函数之外,其它函数既是外部函数又是内部函数。
下面是一个内部函数的例子:
def f():char='hello'def f1():print charf1()
上面的定义在交互方式下的运行情况如下所示:
图1  函数的嵌套定义
二、嵌套函数的作用域
内部函数定义的变量只在内部有效,包括其嵌套的内部函数,但是对外部函数无效,如下例所示:
def f():def f1():x=3print '''目前在函数f1()中:x=''', xdef f2():print '''目前在函数f2()中:x=''', xf2()f1()
对于上面的代码,我们定义了三个函数,其中函数f()是函数f1()的外部函数,而函数f1()又是函数f2()的外部函数。在这三个函数中我们仅定义了一个变量x,并且该变量是在函数f1()中定义的,然后我们分别在函数f1()和它的内部函数f2()中打印该变量的值。在交互式环境下的执行情况如下所示:
图2  内部函数可以使用外部函数的变量
上图说明内部函数可以引用外部函数中定义的变量,但是外部函数却不能引用内部函数定义的变量,例如:
def f():x=6def f1():y=18print '''目前在内部函数f1()中:'''print 'x=',x,'y=',yf1()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 = 0def f1():x = 1def f2():x = 2def f3():x = 3print '目前在函数f3()中,变量x的值为:',xprint '目前在函数f2()中,变量x的值为:',xf3()print '目前在函数f1()中,变量x的值为:',xf2()print '目前在函数f()中,变量x的值为:',xf1()
上述代码中出现了多层嵌套的函数,其中函数f3()嵌套在函数f2()中,函数f2()嵌套在函数f1()中,函数f1()嵌套在函数f()中。但是,每个函数中都对变量x进行了定义,所以各个函数都能在其局部定义域中找到变量x。上述代码在交互式环境下的执行结果如下所示:
图7  在局部作用域中找到变量x的情形
在函数f()中,变量x被赋值为0,根据查找变量时先从当前函数的局部作用域下手的规则,函数f()看到的变量x的值就是0,其它函数依此类推。
现在我们对上述代码稍作修改,将函数f3()和函数f2()中对变量x的定义去掉,看一下在当前函数中找不到变量的定义时的情形,代码如下所示:
def f():x = 0def f1():x = 1def f2():def f3():print '目前在函数f3()中,变量x的值为:',xprint '目前在函数f2()中,变量x的值为:',xf3()print '目前在函数f1()中,变量x的值为:',xf2()print '目前在函数f()中,变量x的值为:',xf1()
上述修改后的代码在交互环境下执行情况如下所示:
图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=',xdef f1():global x #将x声明为全局变量x = 1 #这个赋值语句并没有定义局部变量,而是修改了全局变量的值print '在函数f1()中,x=',xf1()print '现在函数f()中,x=',x
上面的代码以交互式执行,结果如下所示:
图9  在内部函数中使用全局变量的情形
从图10可以看出,当函数f1()执行后,其外部函数f()的局部变量并没有发生变化,但是全局变量x的值却变了。这是因为,当在内部函数f1()中将变量x声明为全局变量后,赋值语句
x = 1
的作用并不是在当前函数中定义一个局部变量,因为这时函数f1()会直接到全局作用域中查找变量,所以赋值语句实际的作用是修改了全局变量x的值。
六、函数嵌套时的执行顺序
当初学者遇到多层嵌套的函数时,对于各个函数的执行顺序经常感到非常头疼,不过不要紧,我们这里向大家介绍一种非常简单的办法,让您轻松读懂代码的执行顺序。
我们曾经说过,在使用def语句定义函数时,Python遇到该语句并不会立即执行其语句体中的代码,只有遇到该函数的调用时,对应def语句的语句体才会被执行。所以,当我们分析嵌套函数的执行顺序时,遇到def语句可以先行跳过(包括其语句体),然后遇到函数调用时,在返回头来查看对应def语句的语句体。我们以下列代码进行说明:
def f():x = 0print '当前正在执行函数f(),其变量x的值为:',xdef f1():x = 1print '当前正在执行函数f1()中,其变量x的值为:',xdef f2():x = 2print '当前正在执行函数f2()中,其变量x的值为:',xdef f3():x = 3print '当前正在执行函数f3()中,其变量x的值为:',xf3()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 = 0def f1():x = 1def f2():x = 2def f3():x = 3print '目前在函数f3()中,变量x的值为:',xf3()print '目前在函数f2()中,变量x的值为:',xf2()print '目前在函数f1()中,变量x的值为:',xf1()print '目前在函数f()中,变量x的值为:',x
七、嵌套作用域的静态性
前面说过,函数内定义的变量局部只有在函数执行时有效,当函数退出后就不能再访问了,例如下列代码:
def f():x = 6print 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。这说明,发生函数嵌套后,如果内部函数引用了外部函数的局部变量,那么外部函数的局部变量将被静态存储,即当函数退出后,其局部变量所占内存也不会被释放。
八、小结
在本文中,我们为读者介绍了函数的嵌套。除了嵌套函数的定义外,我们详细解释了嵌套函数中变量的查找过程以及相关的四种作用域,然后后讲解多层嵌套函数的执行过程,最后说明了嵌套作用域的静态性。为了帮助读者理解本文内容,我们给出了大量示例代码,读者可以利用这些代码实际运行、分析,从而加深理解。
【相关文章】
循序渐进学Python:安装、使用与运行程序
循序渐进学Python之数值类型
循序渐进学Python:三种选择语句
循序渐进学Python之循环语句
循序渐进学Python之函数入门
【责任编辑:red7 TEL:(010)68476606】
原文:循序渐进学Python之函数的嵌套返回开发首页
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
《计算机二级Python语言程序设计考试》第5章:函数和代码复用
第六章:抽象
Python变量作用域规则
《Python编程快速上手——让繁琐的工作自动化》读书笔记2
python基础篇:什么是作用域?具体的作用是什么?
Python中变量的作用域详解
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服