master
/ 4.3 变量的作用域.ipynb

4.3 变量的作用域.ipynb @masterview markup · raw · history · blame

Notebook

4.4 变量作用域

变量的作用域就是指__变量的有效范围__,变量按照作用范围分为两类:__全局变量____局部变量__。</font>

在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为__命名空间__,也被称之为__作用域__。</font>

Python的作用域是__静态的__。</font>

在源代码中__变量名被赋值的位置__决定了该变量能被访问的范围,即Python变量的__作用域__由变量所在源代码中的__位置__决定。</font>

在Python中,并不是所有的语句块中都会产生作用域。只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。

本节我们只讨论与函数相关的变量作用域。

可以简单的说,在__函数外部__声明的变量是__全局变量__,其作用域是__整个文件(或模块)__;在__函数内部__声明的变量是__局部变量__,其作用域是声明这个变量的__函数内部__,在__函数外部不可以访问__。</font>

4.4.1 局部变量

__局部变量是__在函数中定义的变量,包含在def关键字定义的语句块中。

__函数每次被调用时都会创建一个新的对象__。因此,局部变量仅仅是__暂时的__存在,依赖创建该变量的函数是否处于活动状态。函数调用时创建,函数调用结束后销毁并释放该变量的内存。

In [2]:
def myName():
    general_name = '赵云'   # 函数内部定义的局部变量
    print(general_name)     # 输出赵云
    

myName()         # 调用函数myName(),打印 赵云
print(general_name)      # general_name,NameError: name 'general_name' is not defined 
赵云
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_198/2797078214.py in <module>
      5 
      6 myName()         # 调用函数myName(),打印 赵云
----> 7 print(general_name)      # general_name,NameError: name 'general_name' is not defined

NameError: name 'general_name' is not defined

general_name变量是在myName()函数内部定义的,属于局部变量,只能在函数内部访问,在函数外部访问时会返回__NameError__异常。

4.4.2 全局变量

__全局变量是__在模块(文件)层次中定义的变量,每一个模块都是一个__全局作用域__。也就是说,在模块文件__顶层__声明的变量都是全局变量,其__作用域是当前模块文件内__,从变量定义处开始直到文件末尾都是其有效范围,包括__函数外部和函数内部__

注意,上一句话里的“顶层”是一个逻辑概念,指函数之外、和函数定义处于同一层次,并不是一个位置概念(即文件开始处)。

全局变量在模块文件运行的过程中会__一直存在__,占用内存空间,一般建议尽量少定义全局变量。

In [3]:
def myName():
    print(general_name)  # 函数内部访问全局变量general_name,打印其值 赵云

general_name = '赵云'    # 定义全局变量general_name
myName()                 # 调用函数myName()
print(general_name)      # 函数外部访问全局变量general_name,打印其值 赵云
赵云
赵云

函数内部__可以__直接访问和引用全局变量的值,但__不能直接改变__全局变量的值。在函数内部,全局变量应该出现__在赋值符号的右边__,一旦全局变量出现在赋值符号的__左边__,系统会认为这是__重新创建__了一个__同名的局部变量对象__。其值变为在函数体内重赋的新值,同时__屏蔽__外层作用域中的同名变量。

In [4]:
def myName():
    general_name = '张飞'          #函数内定义局部变量,会屏蔽掉同名的全局变量
    print("局部变量的内存地址:{}".format(id(general_name)))
    print("局部变量general_name的值:{}".format(general_name))     # 输出局部变量name的值 张飞 

general_name = '赵云'              #函数外定义的全局变量
print("全局变量的内存地址:{}".format(id(general_name)))
myName()  # 调用函数myName() 
print("全局变量general_name的值:{}".format(general_name))         # 输出全局变量name的值 赵云  
全局变量的内存地址:140358071021072
局部变量的内存地址:140358071020688
局部变量general_name的值:张飞
全局变量general_name的值:赵云

在这个例子里,general_name 本是一个全局变量,myName()函数中被放到了赋值符号左侧重新赋值,这个操作相当于重新创建了一个新的对象“张飞”,并为这个对象贴上一个标签“general_name”。虽然两个对象的名称一样,但分配的内存地址不同,所以是不同的两个对象。因为这个对象和标签都是在函数内部创建的,所以是一个__新的局部变量__,其作用域是这个函数体。

也就是说,__在函数体内__访问general_name,__访问的是局部变量__,其值是“张飞”;__在函数体外__访问变量general_name时__访问的是全局变量__,其值是“赵云”。

这个规则适用于所有全局变量值为__固定数据类型__的情况,如全局变量值为数字型、字符型和元组等。此时,函数体内试图改变全局变量值时,都会创建一个新的局部变量。

在函数内部的变量声明,除非__特别声明为全局变量__,否则均默认为局部变量。 当需要在函数体内声明一个可以在函数体外访问的全局变量的值时,可以使用__global关键字__来声明变量的作用域为全局。global的作用就是把局部变量提升为全局变量。

In [5]:
def myName():
    global general_name     # 声明一个全局变量general_name
    general_name = '张飞'   # 为全局变量general_name赋值
    print("新全局变量general_name的内存地址:{}".format(id(general_name)))
    print("新全局变量general_name的值:{}".format(general_name)) #输出局部变量general_name值‘张飞’


general_name = '赵云'
print("原全局变量name的内存地址:{}".format(id(general_name)))
print("原全局变量name的值:{}".format(general_name))  # 输出全局变量general_name的值‘赵云’
myName()                # 调用函数myName()
print("general_name的值:{}".format(general_name))         # 输出全局变量general_name的值‘张飞’
print("general_name的内存地址:{}".format(id(general_name)))
原全局变量name的内存地址:140358070583632
原全局变量name的值:赵云
新全局变量general_name的内存地址:140358070583536
新全局变量general_name的值:张飞
general_name的值:张飞
general_name的内存地址:140358070583536

从输出结果可以发现,在myName()函数调用后,__函数外再次访问全局变量__general_name时,访问的是最近在函数内部声明的全局变量。

__当全局变量值为列表等可变数据类型__时,如果__函数内部需要修改全局列表变量的值__时,__不需要使用global关键字__进行声明,直接可以使用。这是因为列表等可变数据类型的值的修改是在原内存进行的,只有显式声明才会重新创建对象。

In [ ]:
def myName():
    lsx.append(1)           #改变全局变量lsx的值,未重新创建对象
    lsy = [2,3,4]           #重新创建了对象,此时lsy为局部变量
    print(id(lsx),id(lsy))  #打印内存地址
    print(lsx,lsy)          # lsx 的值为[1] ,lsy 的值为[2, 3, 4]


lsx = []                    #lsx为全局变量
lsy = []                    #lsy为全局变量
print(id(lsx),id(lsy))      # 打印内存地址
myName()                    # 调用函数myName()
print(lsx,lsy)              # lsx的值变为[1],lsy值仍为 [],未发生变化

可以发现,__函数内外lsx的id值相同__,说明二者是相同的对象,函数内只是修改了lsx并__未重新创建对象__

__函数内外lsy的id值不同__,说明二者是不同的对象,函数内部lsy = [2,3,4]这条语句__重新创建对象__[2,3,4],并赋值给变量lsy。