【愚公系列】2021年12月 Python教学课程 13-变量作用域

举报
愚公搬代码 发表于 2021/12/13 09:52:45 2021/12/13
【摘要】 一、变量作用域作用域指的是变量的有效范围。变量并不是在哪个位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内的。通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在 Python 中,没有块级作用域,也就是类似 if 语句块、for 语句块等等是不存在作用域概念的,他们等同于普通的语句。>>> if True:...

一、变量作用域

作用域指的是变量的有效范围。变量并不是在哪个位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内的。

通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在 Python 中,没有块级作用域,也就是类似 if 语句块、for 语句块等等是不存在作用域概念的,他们等同于普通的语句。

>>> if True: # if 语句块没有作用域
 x = 1 
>>> x
1
>>> def func(): # 函数有作用域
 a = 8 
>>> a
Traceback (most recent call last):
 File "<pyshell#3>", line 1, in <module>
 a
NameError: name 'a' is not defined

从上面的例子中,我们可以发现,在 if 语句内定义的变量 x,可以被外部访问,而在函数 func()中定义的变量 a,则无法在外部访问。

通常,函数内部的变量无法被函数外部访问,但内部可以访问;类内部的变量无法被外部访问,但类的内部可以。通俗来讲,就是内部代码可以访问外部变量,而外部代码通常无法访问内部变量。

变量的作用域决定了程序的哪一部分可以访问哪个特定的变量名称。Python 的作用域一共有 4 层,分别是:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域
x = int(2.9) # 内建作用域,查找 int 函数
global_var = 0 # 全局作用域
def outer():
    out_var = 1 # 闭包函数外的函数中
    def inner():
        inner_var = 2 # 局部作用域

前面说的都是变量可以找得到的情况,那如果出现本身作用域没有定义的变量,那该如何寻找呢?

Python 以 L –> E –> G –>B 的规则查找变量,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,最后去内建中找。如果这样还找不到,那就提示变量不存在的错误。例如下面的代码,函数 func 内部并没有定义变量 a,可是 print 函数需要打印 a,那怎么办?向外部寻找!按照 L –> E –> G –>B 的规则,层层查询,这个例子很快就从外层查找到了 a,并且知道它被赋值为 1,于是就打印了 1。

a = 1
def func():
    print(a)

1.全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,被叫做局部变量,定义在函数外的拥有全局作用域的变量,被称为全局变量。(类、模块等同理)

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

a = 1 # 全局变量
def func():
    b = 2 # 局部变量
    print(a) # 可访问全局变量 a
    print(c) # error! 无法访问它内部的 c
    def inner():
        c = 3 # 更局部的变量
        print(a) # 可以访问全局变量 a
        print(b) # b 对于 inner 函数来说,就是外部变量
        print(c)

2.global 和 nonlocal 关键字

我们先看下面的例子:

total = 0 # total 是一个全局变量
def plus( arg1, arg2 ):
    total = arg1 + arg2 # total 在这里是局部变量.
    print("函数内局部变量 total= ", total)
    print("函数内的 total 的内存地址是: ", id(total))
    return total
plus(10, 20)
print("函数外部全局变量 total= ", total)
print("函数外的 total 的内存地址是: ", id(total))

很明显,函数 plus 内部通过 total = arg1 + arg2 语句,新建了一个局部变量 total,它和外面的全局变量 total 是两码事。而如果我们,想要在函数内部修改外面的全局变量total 呢?使用 global 关键字!

global:指定当前变量使用外部的全局变量

total = 0 # total 是一个全局变量
def plus( arg1, arg2 ):
    global total # 使用 global 关键字申明此处的 total 引用外部的 total
    total = arg1 + arg2 
    print("函数内局部变量 total= ", total)
    print("函数内的 total 的内存地址是: ", id(total))
    return total
plus(10, 20)
print("函数外部全局变量 total= ", total)
print("函数外的 total 的内存地址是: ", id(total))

打印结果是:

函数内局部变量 total= 30
函数内的 total 的内存地址是: 503494624
函数外部全局变量 total= 30
函数外的 total 的内存地址是: 503494624

我们再来看下面的例子:

a = 1
print("函数 outer 调用之前全局变量 a 的内存地址: ", id(a))
def outer():
    a = 2
    print("函数 outer 调用之时闭包外部的变量 a 的内存地址: ", id(a))
    def inner():
        a = 3
        print("函数 inner 调用之后闭包内部变量 a 的内存地址: ", id(a))
    inner()
    print("函数 inner 调用之后,闭包外部的变量 a 的内存地址: ", id(a))
outer()
print("函数 outer 执行完毕,全局变量 a 的内存地址: ", id(a))

如果你将前面的知识点都理解通透了,那么这里应该没什么问题,三个 a 各是各的 a,各自有不同的内存地址,是三个不同的变量。打印结果也很好的证明了这点:

函数 outer 调用之前全局变量 a 的内存地址: 493204544
函数 outer 调用之时闭包外部的变量 a 的内存地址: 493204576
函数 inner 调用之后闭包内部变量 a 的内存地址: 493204608
函数 inner 调用之后,闭包外部的变量 a 的内存地址: 493204576
函数 outer 执行完毕,全局变量 a 的内存地址: 493204544

那么,如果,inner 内部想使用 outer 里面的那个 a,而不是全局变量的那个 a,怎么办?

使用 nonlocal 关键字!它可以修改外层的变量。

a = 1
print("函数 outer 调用之前全局变量 a 的内存地址: ", id(a))
def outer():
    a = 2
    print("函数 outer 调用之时闭包外部的变量 a 的内存地址: ", id(a))
    def inner():
        nonlocal a # 注意这行
        a = 3
        print("函数 inner 调用之后闭包内部变量 a 的内存地址: ", id(a))
    inner()
    print("函数 inner 调用之后,闭包外部的变量 a 的内存地址: ", id(a))
outer()
print("函数 outer 执行完毕,全局变量 a 的内存地址: ", id(a))

运行结果:

函数 outer 调用之前全局变量 a 的内存地址: 497726528
函数 outer 调用之时闭包外部的变量 a 的内存地址: 497726560
函数 inner 调用之后闭包内部变量 a 的内存地址: 497726592
函数 inner 调用之后,闭包外部的变量 a 的内存地址: 497726592
函数 outer 执行完毕,全局变量 a 的内存地址: 497726528
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。