「Python Tips」 - Python 变量作用域详解

2022-03-14
3分钟阅读时长

今天在 FastAPI 的一个交流群中回答了个网友的问题,发现自己对 Python 作用域已忘记大半,特意查了些资料,记录备查,希望对朋友们理解有帮助。

阅读本文,你可以了解如下内容: white-list-template.xlsx

Python 中常用的变量作用域有这么几种:局部变量、非局部变量、全局变量,下面我们来举例说明。

局部变量

在函数体或局部范围内声明的变量称为局部变量。参考如下实例:

def foo():
    y = "local"
    print(y)

y = "global"
foo()
print(y)

# local
# global

局部变量尽在局部作用域内有效,可以看到上边代码块有两个同名的变量 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

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

全局变量不允许在函数内部做修改,如需要,则要用到关键字 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

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)

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

可以看到在函数读取全局变量时,字节码是完全一致的,所以他们的行为也是一致的。当函数在修改全局变量时,字节码明显不一样,使用 global定义的会先LOAD_GLOBAL,dis 模块的更多用法可参考 Python 官方文档

总结

至此,Python 常用的变量作用域我们便说完了。总结下,局部变量只能在定义的代码块作用域中访问修改。非局部变量向上查找最近的变量定义,可访问可修改。全局变量可以在全局代码块访问和修改,只能在函数内访问不能修改。

除了上边我们说的,Python 还有内部作用域内置作用域。顾名思义,内部就是多层函数嵌套时,作用域的查找是由内而外的,中间层的作用域就是内部作用域。内置作用域则是 Python 自己内置的一些变量关键字的作用域,由 Python 解释器内部使用。

我是DeanWu,一个努力成为真正SRE的人。

关注公众号「码农吴先生」, 可第一时间获取最新文章。回复关键字「go」「python」获取我收集的学习资料,也可回复关键字「小二」,加我wx拉你进技术交流群,聊技术聊人生~

参考资料

Avatar

DeanWu

大家好,我是 DeanWu,一个努力成为「真正」 SRE 的人。