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()


















![LeetCode[1319]连通网络的操作次数](https://img-blog.csdnimg.cn/img_convert/66d200d112b34fb9a9c783f457a780aa.png)

