「Python Tips」 - Python 变量作用域详解
今天在 FastAPI 的一个交流群中回答了个网友的问题,发现自己对 Python 作用域已忘记大半,特意查了些资料,记录备查,希望对朋友们理解有帮助。
阅读本文,你可以了解如下内容:
Python 中常用的变量作用域有这么几种:局部变量、非局部变量、全局变量,下面我们来举例说明。
# 局部变量
在函数体或局部范围内声明的变量称为局部变量。参考如下实例:
def foo():
y = "local"
print(y)
y = "global"
foo()
print(y)
# local
# global
2
3
4
5
6
7
8
9
10
局部变量尽在局部作用域内有效,可以看到上边代码块有两个同名的变量 y
,函数内的局部变量 y
并不会影响全局变量 y
。
# 非局部变量
Python 3 中增加了关键字 nonlocal
用来创建非局部变量,主要用在未定义局部变量的嵌套函数内。参考如下实例:
def outer():
x = "local"
def inner():
nonlocal x
print("inner:", x)
x = "nonlocal"
inner()
print("outer:", x)
x = 'global'
outer()
print('glabal:', x)
# inner: local
# outer: nonlocal
# glabal: global
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nolocal
关键字会向上一个作用域查找,取最近的一个变量定义。如上代码块中,x 向上最近的定义是 x="local"
,并没有取局部变量 x (严格来说 inner 函数中的 x 已不是局部变量,而是修改赋值的 outer 函数中的变量 x)和全局的变量 x。
# 全局变量
在 Python 中,在函数之外或在全局范围内声明的变量称为全局变量。全局变量允许在函数内部和外部访问,但是不允许在函数内部修改。参考如下实例:
x = "global"
def foo():
print("x inside:", x)
foo()
print("x outside:", x)
# ('x inside:', 'global')
# ('x outside:', 'global')
def foo2():
x = x * 2
print(x)
foo2()
# UnboundLocalError: local variable 'x' referenced before assignment
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
全局变量不允许在函数内部做修改,如需要,则要用到关键字 global
。
x = "global"
def foo():
global x
x = x * 2
print("x inside:", x)
foo()
print("x outside:", x)
# x inside: globalglobal
# x outside: globalglobal
2
3
4
5
6
7
8
9
10
11
global
关键字和 nonlocal
类似也是从其他作用域搜索变量定义,但它仅仅是搜索全局作用域内的变量定义。参考如下实例:
def outer():
x = "local"
def inner():
global x
print("inner:", x)
x = "nonlocal"
inner()
print("outer:", x)
x = 'global'
outer()
print('glabal:', x)
# inner: global
# outer: local
# glabal: nonlocal
def foo2():
global x
x = x * 2
print("x inside:", x)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
inner 函数中 global 定义的变量 x,取的是全局变量中的变量 x。
# 工具模块 dis
官方提供了一个工具模块 dis,可以查看函数在 Python 解释器执行的字节码。我们可以通过它来看具体的作用域调用过程,如下:
$ python3
Python 3.8.10 (default, May 4 2021, 03:05:50)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> x = "global"
>>> def foo():
... print("x inside:", x)
...
>>> def foo1():
... global x
... print("x inside:", x)
...
>>> def foo2():
... global x
... x = x * 2
... print("x inside:", x)
...
>>> def foo3():
... x = x * 2
... print("x inside:", x)
...
>>> from dis import dis
>>> dis(foo)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('x inside:')
4 LOAD_GLOBAL 1 (x)
6 CALL_FUNCTION 2
8 POP_TOP
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
>>> dis(foo1)
3 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('x inside:')
4 LOAD_GLOBAL 1 (x)
6 CALL_FUNCTION 2
8 POP_TOP
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
>>> dis(foo2)
3 0 LOAD_GLOBAL 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_MULTIPLY
6 STORE_GLOBAL 0 (x)
4 8 LOAD_GLOBAL 1 (print)
10 LOAD_CONST 2 ('x inside:')
12 LOAD_GLOBAL 0 (x)
14 CALL_FUNCTION 2
16 POP_TOP
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
>>> dis(foo3)
2 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_MULTIPLY
6 STORE_FAST 0 (x)
3 8 LOAD_GLOBAL 0 (print)
10 LOAD_CONST 2 ('x inside:')
12 LOAD_FAST 0 (x)
14 CALL_FUNCTION 2
16 POP_TOP
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
可以看到在函数读取全局变量时,字节码是完全一致的,所以他们的行为也是一致的。当函数在修改全局变量时,字节码明显不一样,使用 global
定义的会先LOAD_GLOBAL
,dis 模块的更多用法可参考 Python 官方文档 (opens new window)。
# 总结
至此,Python 常用的变量作用域我们便说完了。总结下,局部变量只能在定义的代码块作用域中访问修改。非局部变量向上查找最近的变量定义,可访问可修改。全局变量可以在全局代码块访问和修改,只能在函数内访问不能修改。
除了上边我们说的,Python 还有内部作用域和内置作用域。顾名思义,内部就是多层函数嵌套时,作用域的查找是由内而外的,中间层的作用域就是内部作用域。内置作用域则是 Python 自己内置的一些变量关键字的作用域,由 Python 解释器内部使用。
我是DeanWu,一个努力成为真正SRE的人。
关注公众号「码农吴先生」, 可第一时间获取最新文章。回复关键字「go」「python」获取我收集的学习资料,也可回复关键字「小二」,加我wx拉你进技术交流群,聊技术聊人生~
# 参考资料
- https://www.programiz.com/python-programming/global-keyword
- https://docs.python.org/3/library/dis.html https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python
- https://docs.python.org/3/faq/programming.html#how-do-i-share-global-variables-across-modules