什么是作用域

作用域, 直观的概念就是作用范围, 比如变量的可用范围, 方法的可用范围, 类的可用范围,

在这个范围内, 我们可以访问这个对象(变量/方法/类),

简而言之, 作用域是对变量/方法/类的访问的范围的限制

变量作用域

变量作用域 就是对变量访问的范围的限制,

比如, 定义了某个变量a, 那么在什么地方可以访问这个变量呢, 以下是我的总结

函数作用域

这是最直观的作用域, 我们在函数内定义了一个变量, 在这个函数内可以直接访问, 在函数外则访问不到

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))

test_a() # test_a l_name:Yi_Zhi_Yu

以上代码, 运行时能输出的变量l_name 处于 test_a这个函数的函数作用域内, 在函数外不可访问

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))

test_a() # test_a l_name:Yi_Zhi_Yu
print(l_name)

报错

test_a l_name:Yi_Zhi_Yu
Traceback (most recent call last):
  File "/xxx/path/a.py", line 17, in <module>
    print(l_name)
NameError: name 'l_name' is not defined

提示 l_name 未定义, 但函数内的变量依然打印出来了

文件作用域

a.py 文件中

f_name = 'tony'

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))
    print('test_a f_name:{}'.format(f_name))

test_a()
#print(l_name)
print('file f_name: {}'.format(f_name))

输出如下

test_a l_name:Yi_Zhi_Yu
test_a f_name:tony
file f_name: tony

文件中定义的变量, 在函数内和函数外都访问到了

那么, 假如变量在某个函数内修改了, 那么这个变量在函数外会发生什么变化呢

f_name = 'tony'

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))
    print('test_a f_name:{}'.format(f_name))
    f_name = 'tony2' # 在这里对文件作用域内的变量(f_name)做修改 
    print('test_a new f_name:{}'.format(f_name))

test_a()
# print(l_name)
print('file f_name: {}'.format(f_name))

运行时确报错了

test_a l_name:Yi_Zhi_Yu
Traceback (most recent call last):
  File "/xxx/path/a.py", line 18, in <module>
    test_a()
  File "/xxx/path/a.py", line 14, in test_a
    print('test_a f_name:{}'.format(f_name))
UnboundLocalError: local variable 'f_name' referenced before assignment

而且提示的出错的位置是在函数内还未对f_name 做修改的时候, 提示的错误是 local variable 'f_name' referenced before assignment,

看起来是打印了一个未定义的局部变量

可以看出, 定义的文件作用域内的变量是不允许直接修改的, 否则会被当做函数的局部变量

那么如果在函数内重新定义了该变量, 在函数外的同名变量又会怎么样呢

f_name = 'tony'

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))
    # print('test_a f_name:{}'.format(f_name)) # 去掉这里
    f_name = 'tony2'
    print('test_a new f_name:{}'.format(f_name))

test_a()
# print(l_name)
print('file f_name: {}'.format(f_name))

输出如下:

test_a l_name:Yi_Zhi_Yu
test_a new f_name:tony2
file f_name: tony

可以看出来, 在函数内部重新定义的变量, 就被视为局部变量, 影响的也只是所在的局部变量作用域, 并不会影响文件作用域, 甚至是全局作用域

全局作用域

如果我们在另一个模块文件b.py中打印a.py里的变量会怎么样

import a
print('b.py a.f_name: {}'.format(a.f_name))

如果是通过模块引用的方式, 如上, 结果可以正常输出

test_a l_name:Yi_Zhi_Yu
test_a new f_name:tony2
file f_name: tony
b.py a.f_name: tony  # 这里就是b.py里的a.f_name 输出

那如果是直接print(f_name)

import a
print('b.py a.f_name: {}'.format(a.f_name))
print('b.py f_name: {}'.format(f_name))

报错了

test_a l_name:Yi_Zhi_Yu
test_a new f_name:tony2
file f_name: tony
b.py a.f_name: tony
Traceback (most recent call last):
  File ""/xxx/path/b.py", line 8, i <module>
    print('b.py f_name: {}'.format(f_name))
NameError: name 'f_name' is not defined

看来, a.py 模块文件中的文件作用域的变量是不能在该文件之外直接访问的

那有没有什么方式可以实现呢

也许有人说用global关键字, 我们来试一下

import a

print('b.py a.f_name: {}'.format(a.f_name))
global f_name
print('b.py f_name: {}'.format(f_name))

运行b.py, 也是同样的错误

看来, python 中是不允许跨文件(模块)的变量直接访问(print(f_name))的, 必须通过模块引用的方式print(a.f_name)

python 的全局作用域也仅限于单个的模块文件中, 即所谓的全局变量就是单个模块文件内的全局变量

那么global关键字是如何使用的呢

global 关键字

其实在上面的一个场景(文件作用域)中, 我们想在函数中对模块文件中的全局变量 f_name 进行修改和打印, 但报错了,

因为当在函数内对全局变量做修改时, 该变量就被当成了局部变量, 而局部变量需要先定义才能使用

而且在函数中对f_name 做修改后, 并不会影响函数外的f_name

那假如我们确实想在函数内修改全局变量怎么办?

这个时候global 就派上用场了

f_name = 'tony'

def test_a():
    l_name = 'Yi_Zhi_Yu'
    print('test_a l_name:{}'.format(l_name))
    print('test_a f_name:{}'.format(f_name))
    global f_name # 这里使用global关键字
    f_name = 'tony2'
    print('test_a new f_name:{}'.format(f_name))

test_a()
# print(l_name)
print('file f_name: {}'.format(f_name))

运行a.py输出如下

/xxx/path/a.py:15: SyntaxWarning: name 'f_name' is used prior to global declaration
  global f_name
test_a l_name:Yi_Zhi_Yu
test_a f_name:tony
test_a new f_name:tony2
file f_name: tony2 # 函数外的全局变量

我们可以看到, 函数外的全局变量f_name 也被更改了

注意, 上面还有一个SyntaxWarning, 提示在使用global 关键字前, f_name 被优先当做全局变量使用了, 当然, 这里不推荐这么使用, 如果真的有必要, 在test_a的第一行就使用global f_name 即可

right