Python教程-变量作用域

变量作用域
提到函数就必须介绍变量的作用域相关内容。
作用域指的是变量的有效范围。并非所有位置都可以访问变量,访问权限取决于变量在哪里赋值,即在哪个作用域内。
在大多数编程语言中,变量的作用域可以按代码结构形式划分为块级、函数级、类级、模块级和包级等级别,从小到大。但在Python中,没有块级作用域的概念。例如,if语句块、for语句块、with上下文管理器等,它们与普通语句等同,不存在独立的作用域。
>>> 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)
全局变量和局部变量
变量的作用域分为局部作用域和全局作用域。
局部作用域指的是在函数内部定义的变量,只能在其所在的函数内部访问。这些变量被称为局部变量,其作用范围仅限于函数内部。
全局作用域指的是在函数外部定义的变量,可以在整个程序范围内访问。这些变量被称为全局变量,其作用范围包括函数内部和外部。
需要注意的是,调用函数时,函数内部声明的变量名称会被加入到局部作用域中,而全局变量可以在函数内部访问。但是在函数内部,如果存在与全局变量同名的局部变量,局部变量将覆盖全局变量。
a = 1 # 全局变量
def func():
b = 2 # 局部变量
print(a) # 可访问全局变量a,无法访问它内部的c
def inner():
c = 3 # 更局部的变量
print(a) # 可以访问全局变量a
print(b) # b对于inner函数来说,就是外部变量
print(c)
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,怎么办?用global关键字?先试试看吧:
a = 1
print("函数outer调用之前全局变量a的内存地址: ", id(a))
def outer():
a = 2
print("函数outer调用之时闭包外部的变量a的内存地址: ", id(a))
def inner():
global a # 注意这行
a = 3
print("函数inner调用之后闭包内部变量a的内存地址: ", id(a))
inner()
print("函数inner调用之后,闭包外部的变量a的内存地址: ", id(a))
outer()
print("函数outer执行完毕,全局变量a的内存地址: ", id(a))
运行结果如下,很明显,global使用的是全局变量a。
函数outer调用之前全局变量a的内存地址: 494384192
函数outer调用之时闭包外部的变量a的内存地址: 494384224
函数inner调用之后闭包内部变量a的内存地址: 494384256
函数inner调用之后,闭包外部的变量a的内存地址: 494384224
函数outer执行完毕,全局变量a的内存地址: 494384256
那怎么办呢?使用nonlocal
关键字!它可以修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量。将global a
改成nonlocal a
,代码这里我就不重复贴了,运行后查看结果,可以看到我们真的引用了outer函数的a变量。
函数outer调用之前全局变量a的内存地址: 497726528
函数outer调用之时闭包外部的变量a的内存地址: 497726560
函数inner调用之后闭包内部变量a的内存地址: 497726592
函数inner调用之后,闭包外部的变量a的内存地址: 497726592
函数outer执行完毕,全局变量a的内存地址: 497726528
面试真题:
不要上机测试,请说出下面代码的运行结果:
a = 10
def test():
a += 1
print(a)
test()
很多同学会说,这太简单了!函数内部没有定义a,那么就去外部找,找到a=10,于是加1,打印11!
我会告诉你,这段代码有语法错误吗?a += 1
相当于a = a + 1
,按照赋值运算符的规则是先计算右边的a+1
。但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用global声明了它是外部变量。很明显,我们没有在函数内部定义变量a,所以会弹出局部变量在未定义之前就引用的错误。
更多的例子:
再来看一些例子(要注意其中的闭包,也就是函数内部封装了函数):
name = 'jack'
def outer():
name='tom'
def inner():
name ='mary'
print(name)
inner()
outer()
上面的题目很简单,因为inner函数本身有name变量,所以打印结果是mary。那么下面这个呢?
name ='jack'
def f1():
print(name)
def f2():
name = 'eric'
f1()
f2()
这题有点迷惑性,想了半天,应该是‘eric’吧,因为f2函数调用的时候,在内部又调用了f1函数,f1自己没有name变量,那么就往外找,发现f2定义了个name,于是就打印这个name。错了!!!结果是‘jack’!
Python函数的作用域取决于其函数代码块在整体代码中的位置,而不是调用时机的位置。调用f1的时候,会去f1函数的定义体查找,对于f1函数,它的外部是name ='jack'
,而不是name = 'eric'
。
再看下面的例子,f2函数返回了f1函数:
name = 'jack'
def f2():
name = 'eric'
return f1
def f1():
print(name)
ret = f2()
ret()
仔细回想前面的例子,其实这里有异曲同工之妙,所以结果还是‘jack’。