
4.1.6 变量作用域
当引入函数的概念后,就出现了变量作用域的问题。即在程序中定义的变量是有作用范围的,变量起作用的范围称为变量的作用域(Variable Scope)。变量的作用域表现为有的变量可以在整个程序或其他程序中进行引用,有的变量则只能在局部范围内引用。一个变量在函数外部定义和在函数内部定义,其作用域是不同的。如果用特殊的关键字定义一个变量,也会改变其作用域。
1.变量的作用域分类
一个变量在函数外部定义与在函数内部定义,其作用域是不同的。例如,在函数内部定义的变量一般为局部变量,拥有局部作用域;在函数外部主程序中定义的变量,不属于任何函数,一般为全局变量,拥有全局作用域。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
局部变量只能在其被声明的函数内部访问,函数外部的变量不能访问函数内部的变量,而内部变量能访问外部的变量。全局变量可以在整个程序范围内访问,但是当函数外部和内部有同名变量时,外部变量会被内部变量屏蔽掉。
在Python中,所有的变量根据作用域可归纳为以下4种。
1)L(Local):局部作用域,即在函数中定义的变量。
2)E(Enclosing):嵌套父级函数的局部作用域,即包含此函数上级函数的局部作用域,但不是全局的。
3)G(Global):全局变量,就是模块级别定义的变量。
4)B(Built-in):系统固定模块、内置函数所在模块范围内的变量。
Python与其他程序语言不同,Python没有提供private、public这样的访问修饰符。
2.局部变量
局部变量(Local Variable)是指在函数内部定义的普通变量,该变量只在该函数内起作用,别的函数不可访问。当函数运行结束后,在该函数内部定义的局部变量被自动删除而不可访问。对于在函数内定义的普通变量,包括在类(Class)中的函数内定义的普通变量,都是局部变量,也称自动临时变量。
局部变量与函数外具有相同名称的其他变量没有任何关系,彼此互不干扰,即变量名称对于函数来说是局部的。所有局部变量的作用域是该变量被定义的块,从该变量被定义处开始。
【例4-18】 局部变量示例。

运行结果如下:

从结果看出,主程序与函数中的同名变量是独立的,互不影响。
在编写一个较复杂的程序时,会有多个函数,此时应该把注意力集中在这些相对独立的函数内。其中所用到的变量如果都是局部变量,则无论怎样处理都不会影响到外界。如果使用非局部变量,考虑不周时容易引起麻烦。所以,为安全起见,函数体内应尽可能用局部变量。
3.全局变量
全局变量(Global Variable)指的是能同时作用于函数内外的变量,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。如果想要在函数内部给一个定义在函数外的变量赋值,那么这个变量就不能是局部的,其作用域必须为全局的。可以在函数中用global和nonlocal关键字定义全局变量。如果全局变量的值在函数内部被重新赋值,这个赋值结果也将反映在函数外部;同样,在函数外部为一个全局变量赋值,赋值效果也同样反映在函数内部。
在函数内部声明全局变量的语法格式为:

如果要修改嵌套作用域(也叫Enclosing作用域或外层非全局作用域)中的变量,则需要nonlocal关键字。在嵌套函数内部声明全局变量的语法格式为:

注意,global或nonlocal语句只能在函数中使用,不能在主程序中声明全局变量。在使用global或nonlocal关键字修饰变量时,不能直接给变量赋初值,否则会引发语法错误。
定义全局变量的方式有以下两种。
(1)该变量已经在函数体外定义
如果一个变量已在函数外定义,当要在函数内为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内用global声明这个变量,将其定义为全局变量。
【例4-19】 在函数内为变量y赋值。

运行结果为:

取消注释行,程序运行到该行显示错误提示“UnboundLocalError:local variable'y'referenced before assignment”。
x变量在函数内没有进行赋值操作,函数内直接引用外部变量是没有问题的。但是,y变量在函数体中被赋值,那么y就被认定为局部变量,在赋值前引用则该变量未定义,因而出错。如果要将y变量改成全局变量,则要在函数内用global明确声明要使用已经定义的同名全局变量。
【例4-20】 使用global把y修改为全局变量。


运行结果如下:

从结果看,在函数中也可以访问主程序中定义的变量。
【例4-21】 在函数体内使用外部定义的全局变量。

运行结果如下:

在本例中,函数外部已经定义了一个变量x,此时它首先是一个局部变量,在函数func中用global声明x为全局变量。当在函数内把值赋给x时,也就是在给函数外的x赋值,因此在函数内对x的值改变的效果也反映在函数外。
(2)在函数体内新定义全局变量
在函数内部使用global关键字将一个新的变量声明为全局变量,在函数外没有定义该变量,在调用这个函数后,将增加这个新的全局变量。
【例4-22】 如下程序中,函数内部声明的全局变量i是新增加的。

运行结果如下:

从运行结果看,在函数外部和内部定义的全局变量,都可以在函数内部和外部访问和修改。
【例4-23】 把函数内部定义的变量声明为全局变量。

运行结果如下:

在本例中,函数外部没有声明x,在函数内部用global声明x,在调用func函数之后,x将作为一个全局变量,可以看到,在函数内部为x赋值20,在函数外面输出x的值也为20。
【例4-24】 用nonlocal关键字修改嵌套作用域中的变量。

运行结果如下:

【例4-25】 设计一个模拟幸运数字机的游戏。设幸运数字为7,每次由计算机随机产生3个0~10之间的随机数,当这3个随机数中有一个数字为7时,就算赢了一次。要求利用全局变量来累计获胜次数。
为了使每次调用函数产生随机数的过程中,累计次数i和获胜次数wins的值保持不变,应该将其声明为全局变量,如果以局部变量声明i和wins,则每次调用函数时都会重新设置该变量的值。程序如下:


运行结果如下(因为是随机数,每次运行的结果都是不相同的):

另外,除非真的有必要,否则应尽量避免使用全局变量,因为全局变量会增加不同函数之间的隐式耦合度,从而降低程序的可读性,并使得程序测试和纠错变得困难。