Python中的作用域及global用法

hresh 600 0

Python中的作用域及global用法

作用域

Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。

  • 函数定义了本地作用域,而模块定义的是全局作用域。 如果想要在函数内定义全局作用域,需要加上 global 修饰符。
  • 变量名解析:LEGB 原则
    当在函数中使用未认证的变量名时,Python 搜索 4 个作用域[本地作用域(L)(函数内部声明但没有使用 global 的变量),之后是上一层结构中 def 或者 lambda 的本地作用域(E),之后是全局作用域(G)(函数中使用 global 声明的变量或在模块层声明的变量),最后是内置作用域(B)(即 python 的内置类和函数等)]并且在第一处能够找到这个变量名的地方停下来。如果变量名在整个的搜索过程中
    都没有找到,Python 就会报错。
    补:上面的变量规则只适用于简单对象,当出现引用对象的属性时,则有另一套搜索规则:属性引用搜索一个或多个对象,而不是作用域,并且有可能涉及到所谓的"继承"。
  • 以上基于 http://blog.csdn.net/carolzhang8406/article/details/6855525

在探讨 global 修饰符之前,我们有必要了解一下Python数据类型。
Python3 中有六个标准的数据类型:
- Number(数字)
- String(字符串)
- List(列表)
- Tuple(元组)
- Set(集合)
- Dictionary(字典)

Python3 的六个标准数据类型中:

  • 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);
  • 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

关于数据类型可变与不可变,有如下定义:

不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。
可变数据类型:当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。
总结:不可变数据类型更改后地址发生改变,可变数据类型更改地址不发生改变。

global修饰符

下面探讨 global 修饰符的用法:

1. 不可变数据类型

这里我们就以String(字符串)类型为例进行分析。

  • 首先是pythond的一个奇异现象,在模块层面定义的变量(无需global修饰),如果在函数中没有再定义同名变量,可以在函数中当做全局变量使用:
    mess = 'hello'
    
    def foostr():
        # global mess
        print('变量在内存中的地址:{}'.format(id(mess)))
    
    foostr()
    print('变量在内存中的地址:{}'.format(id(mess)))
    print(mess)
    

    输出结果为:

    变量在内存中的地址:2559622609304
    变量在内存中的地址:2559622609304
    hello
    

    代码中取消 global 的注释,输出的变量地址依然是一致的。

  • 如果在函数中有再赋值/定义(因为 python 是弱类型语言,赋值语句和其定义变量的语句一样)。

    mess = 'hello'
    
    def foostr():
        # global mess
        mess = 'world'
        print('变量在内存中的地址:{}'.format(id(mess)))
    
    foostr()
    print('变量在内存中的地址:{}'.format(id(mess)))
    print(mess)
    

    输出结果如下:

    变量在内存中的地址:1821595892736
    变量在内存中的地址:1821595892120
    hello
    

    上述代码可以正常运行且输出为 hello,未得到想要的 world 结果。

    根据 LEGB 原则分析可知,是因为模块上面声明的 mess 变量在内存中的地址和在函数内部声明的 mess 变量内存地址不一样。

  • 如果我可能在函数使用某一变量后又对其进行修改(也即再赋值,需要注意的是,这里的数据类型是不可变的,所以所有的修改都是重新赋值操作),怎么让函数里面使用的变量是模块层定义的那个全局变量而不是函数内部的局部变量呢?这时候 global 修饰符就派上用场了。

    mess = 'hello'
    
    def foostr():
        global mess
        mess = 'world'
        print('变量在内存中的地址:{}'.format(id(mess)))
    
    foostr()
    print('变量在内存中的地址:{}'.format(id(mess)))
    print(mess)
    
    变量在内存中的地址:1747608435656
    变量在内存中的地址:1747608435656
    world
    

    在用 global 修饰符声明 mess 是全局变量的 mess 后(注意,global 语句不允许同时进行赋值如 global mess = ‘world'是不允许的),上述输出是 world,得到了我们想要的效果。

2. 可变数据类型

这里我们以 Dictionary(字典)为例进行分析。

在模块层面定义的变量(无需 global 修饰),如果在函数中没有再定义同名变量,可以在函数中当做全局变量使用,可以直接进行内容修改:

c = {}

def foo():
    # global c
    c['a'] = 1
    print('变量在内存中的地址:{}'.format(id(c)))

print(c)
foo()
print('变量在内存中的地址:{}'.format(id(c)))
print(c)

输出结果如下:

{}
变量在内存中的地址:2231654522816
变量在内存中的地址:2231654522816
{'a': 1}

测试过程中,不管是否取消 global 得注释,结果中变量在内存中得地址都是一致的。
究其原因,是因为我们在函数内部做的不是重新赋值操作,而是在原有字典对象身上做的修改操作,而非“=”赋值操作,可以不用 golbal 声明。

c = {}

def foo():
    # global c
    c = {'a':1}
    print('变量在内存中的地址:{}'.format(id(c)))

print(c)
foo()
print('变量在内存中的地址:{}'.format(id(c)))
print(c)

输出结果如下:

{}
变量在内存中的地址:2388490697608
变量在内存中的地址:2388490624888
{}

可以看到,没有使用 global 进行声明时,字典对象并未在函数中被修改,因为函数中重新声明的字典对象 c 是一个全新的,该变量仅为本地作用域;而非全局作用域中的变量 c。

往深处分析,因为 python 是弱类型语言,赋值语句和其定义变量的语句一样,所以 c = {'a':1}这句中,它是”有歧义的“,因为它既可以是表示引用全局变量 c,也可以是创建一个新的局部变量,所以在 python 中,默认它的行为是创建局部变量,除非显式声明 global,global 定义的本地变量会变成其对应全局变量的一个别名,即是同一个变量。

总结

当全局变量是数字、字符串、元组这些不可变数据类型时,在函数重新定义赋值时要用 global 进行声明;当全局变量是列表、集合、字典这类可变类型时,在函数内部利用 list 或 dict 的内置函数进行修改时,可以不用 global 声明”

发表评论 取消回复
表情 图片 链接 代码

分享