1. 定义
作用域就是一个 python 程序可以直接访问命名空间的正文区域。
在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。
2. 四种作用域
2.1
局部作用域(Local,简称L)
它的范围是一个函数/方法的内部。函数的参数,函数内定义的各种变量或对象都属于局部作用域`。
搜索变量时首先被搜索。这是最内层的作用域。
# 函数参数num1和num2, 以及value是局部变量, 属于局部作用域
# 在getSum()的整个函数体内都可以被直接访问
def getSum(num1, num2):
value = num1 + num2
return value
2.2
嵌套作用域(Enclosing,简称E)
主要用在有嵌套函数的场景,用来实现闭包
。包含了非局部(non-local)也非全局(non-global)的变量
。
比如两个嵌套函数,一个函数A 里面包含一个函数B ,如果在函数B中访问了函数A中的局部变量,那么在函数B就会创建一个__closure__
属性,用于保存A中这些被B访问的局部变量。这个__closure__
属性中的变量的作用域就是嵌套作用域。
# outer()是外部函数, inner()是内部函数.
# 变量a是outer()中的局部变量, 是局部作用域, outer函数体中的任何位置都可以直接访问变量a;
# add函数也是一个特殊的outer函数中的局部变量;
# inner()是一个内部函数, 访问了外部函数outer()中的局部变量a, 因此外层函数outer()中的变量a被放入inner()的_closure__属性中;
# a就属于嵌套作用域, 在嵌套函数inner()的任何位置都可以直接访问变量a.
def outer():
a = 100
def inner(b):
return a + b
return inner
my_add = outer()
print(my_add(10))
print(my_add(20))
2.3
全局作用域(Global,简称G)
它的范围是当前模块的任何位置
。模块中的顶层区域导入的其它模块、定义的全局变量、函数和类,都属于全局作用域。
# 在模块的顶层区域定义的都是全局变量, g1是属于全局作用域, 在本模块的任何位置都可以被访问;
# test也是一个特殊的全局变量, 变量名是test, 属于全局作用域, 在本模块的任何位置都可以被访问;
g = 100
def test():
print("访问全局变量g={}".format(g)) # 访问全局变量g
# 函数调用(访问全局变量test)
test()
2.4
内置作用域(Built-in,简称B)
它的范围是所有模块的任何位置
。python内置的数据类型、内置函数、标准异常等都属于内置作用域。
# max是内置函数, 它的作用域类型是Builtin作用域;
# 在模块的任何位置都可以访问它;
def test():
print(max(1, 2))
print(max(1, 2))
3. 变量的查找顺序
规则顺序: L –> E –> G –> B
。
(1)
先在局部作用域找;
(2)
找不到,就去嵌套作用域找(例如存在闭包的场景);
(3)
再找不到,就会去全局作用域找;
(4)
再找不到,就去内置作用域中。如果还是没找到,会抛出NameError的异常。
上述的4个过程中,任何一个找到,就会停止查找
,使用找到的这个变量的值。注意:变量查找时,LGB三个作用域是一定存在的,E作用域不一定存在。
4. 作用域和命名空间的关系
作用域是建立在命名空间之上的一个虚拟的概念,所有的变量都是存储在某个命名空间里面的,作用域决定了这些变量的可访问范围(可见性)。当命名空间被创建后,其对应的作用域就被确定(或是形成)。当一个变量被定义后,就会被加入到该代码行所在的命名空间里,这时候,该命名空间对应的作用域范围的任何位置,都可以访问该变量。
5. 哪些代码能生成命名空间和命名作用域
python 中只有模块(module),类(class)、函数(def、lambda)以及列表推导才会创建新的命名空间并形成新的作用域
,其它的代码块(如
if/elif/else/、try/except、for/while等)是不会创建新的命名空间以及形成新的作用域
,也就是说这些语句内定义的变量,外部也可以访问
。
# 1.实例中text变量定义在if语句块中, 但外部还是可以访问的.
if True:
text = "HelloWorld."
print(text)
# 2.如果将text定义在函数中, 则它就是局部变量, 外部不能访问.
# 企图在外部访问函数内部定义的变量, 错误. fNameError: name 'count' is not defined
def test():
count = 2
print(count)
6. global 和 nonlocal关键字
6.1
global关键字
要想在局部作用域中修改全局变量的值,必须使用global关键字对变量进行修饰,才能修改成功
。
# [demo1]
# 以下代码企图用来修改全局变量g_count的值, 修改失败.
# fun1()中的g_count是局部变量, 修改它, 并不会对全局变量g_count造成任何影响;
# 所以最终输出结果是:g_count:> 99
g_count = 99
def fun1():
g_count = 66
fun1()
print("g_count:> ", g_count)
# [demo2]
# 通过global关键字进行修饰, 修改成功.
# 程序运行结果:g_count:> 100
g_count = 99
# 要想修改全局变量g_count, 则需要使用global关键字
def fun1():
global g_count
g_count = 100
fun1()
print("g_count:> ", g_count)
6.2
nonlocal关键字
在嵌套函数中修改嵌套作用域中的变量(也就是修改外层函数的局部作用域中的变量),必须使用 nonlocal 关键字对变量进行声明
。
# [demo1]
# 企图在inner()中修改外部函数中的局部变量number的值, 修改失败.
# 输出结果是: number is:> 10
def outer():
number = 10
def inner():
number = 10000
inner()
print("number is:> ", number)
outer()
# [demo2]
# 通过关键字nonlocal进行修饰, 则修改成功.
# 输出结果是: number is:> 10
def outer():
number = 10
def inner():
nonlocal number
number = 10000
inner()
print("number is:> ", number)
outer()
6.3
分析
上面的2个例子(5.1和5.2),在不加相应的关键字的情况下,都没能如期修改外部作用域中的变量的值。那么,没加关键字的情况下,肯定是生成了新的变量,导致赋值给了这个新的变量。这个新变量是当前作用域中的局部变量
。我们可以通过locals()
来加以验证。
globals()
:以字典形式返回当前位置可以访问的所有的全局变量的信息。
locals()
:以字典形式返回当前位置可以访问的所有的局部变量的信息。
(1)
修改6.1中的[demo1]
# 通过打印发现, fun1()中的g_count确实是一个局部变量.
g_count = 99
def fun1():
g_count = 66
print(locals())
fun1()
print("g_count:> ", g_count)
(2)
修改6.2中的[demo1]
# 通过打印发现, inner()中的number确实是一个局不变量.
def outer():
number = 10
def inner():
number = 10000
print(locals())
inner()
print("number is:> ", number)
outer()
6.4
特殊情况
# 执行以下代码, 将发生报错;
# 错误信息为局部作用域引用错误, 因为test函数中的a使用的是局部, 未定义, 无法修改.
a = 10
def test():
a = a + 1
print(a)
test()
那么该如何修改呢?
(1)
使用global进行修饰
# 输出:11
a = 10
def test():
global a # global
a = a + 1
print(a)
test()
(2)
作为函数参数传递
# 输出:11
a = 10
def test(a):
a = a + 1
print(a)
test(a)
7. 其他一些情况
7.1
在局部作用域中导入(import)模块
def test():
# 此时sys属于test中的局部作用域
import sys
print(sys.path)
test()
# 全局作用域不能访问局部作用域中的变量
print(sys.path)
7.2
变量访问出现UnboundLocalError
对于这种情况,python会抛出UnboundLocalError异常
。对于print(a)来说,python会认为是访问这个局部变量a而不是外面的全局变量a
,但这个局部变量还没有被定义,不能被访问。所以就抛出这个异常。
def test():
print(a) # 会抛出UnboundLocalError异常
a = 100
a = 1
test()
7.3
访问列表推导式中的变量出现NameError异常
def test():
ls = [i for i in range(6)]
print(i) # 抛出NameError异常
test()
因为列表推导式会创建自己的命名空间并形成自己的作用域
,因此变量i并不属于外层的test()中的局部作用域,在进行变量i的查找时,不会在内层作用域进行查找。
7.4
函数调用时
a = 1 # 全局变量a
def test1():
print(a)
def test2():
# 创建一个新的局部变量a, 属于test2的局部作用域
a = 200
# 在这里只是调用函数test1, test1中的局部作用域和这里的test2的局部作用域没有任何关系
# 也不存在嵌套函数的关系, 所以test1中的a的输出值是全局作用域中的a的值1,而不是这里的a=200
test1()
test2()