访问封闭作用域中定义的变量

30

来自Google风格指南关于词法作用域的解释:

嵌套的Python函数可以引用封闭函数中定义的变量,但不能对其进行赋值。

这两点起初似乎都没有问题:

# Reference
def toplevel():
    a = 5
    def nested():
        print(a + 2)
    nested()
    return a
toplevel()
7
Out[]: 5

# Assignment
def toplevel():
    a = 5
    def nested():
        a = 7 # a is still 5, can't modify enclosing scope variable
    nested()
    return a
toplevel()
Out[]: 5

那么,为什么在嵌套函数中同时使用引用和赋值会导致异常?
# Reference and assignment
def toplevel():
    a = 5
    def nested():
        print(a + 2)
        a = 7
    nested()
    return a
toplevel()
# UnboundLocalError: local variable 'a' referenced before assignment

1
只是一个提示,print(a+2);a=7 这个组合不起作用,但是 a=7;print(a+2) 这个组合可以。 - DjaouadNM
所涉及的指南据我了解是针对Python 2.x编写的,该版本缺少“nonlocal”关键字。在3.x中,“nonlocal”可用于解决所描述的问题。 - Karl Knechtel
2个回答

37

在第一种情况下,您正在引用一个名为nonlocal的变量,这是可以的,因为没有叫做a的局部变量。

def toplevel():
    a = 5
    def nested():
        print(a + 2) # theres no local variable a so it prints the nonlocal one
    nested()
    return a
在第二种情况下,您创建了一个名为a的本地变量,这也是可以的(本地a与非本地变量不同,因此原始的a未被更改)。
def toplevel():
    a = 5 
    def nested():
        a = 7 # create a local variable called a which is different than the nonlocal one
        print(a) # prints 7
    nested()
    print(a) # prints 5
    return a

在第三种情况下,您创建了一个局部变量,但在此之前有 print(a+2),这就是引发异常的原因。因为print(a+2)将引用在该行之后创建的局部变量a

def toplevel():
    a = 5
    def nested():
        print(a + 2) # tries to print local variable a but its created after this line so exception is raised
        a = 7
    nested()
    return a
toplevel()
为了实现你想要的结果,在内部函数中需要使用 nonlocal a
def toplevel():
    a = 5
    def nested():
        nonlocal a
        print(a + 2)
        a = 7
    nested()
    return a

1
在第三种情况下,nested 是如何“知道”查找局部变量而不是使用封闭函数的 a 的?除此之外,我理解你的答案,但是这个方面我不太明白,因为 Python 是逐行解释的。 - Brad Solomon
2
尽管它是一种解释性语言,在运行之前整个语法都会被检查。请看这个这个 - Mohd
Python的语义不依赖于它是解释型语言。即使CPython是Python的规范“解释器”实现,它也不是“逐行解释”的。它将程序文本编译成字节码,然后在cpython虚拟机(VM)上运行该字节码。字节码是针对cpython VM的特定“CPU”的机器码。 - Kuba hasn't forgotten Monica

19

对于任何偶然发现这个问题的人,除了这里被接受的答案之外,Python文档中还简洁地回答了这个问题:

Python文档

This code:

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

works, but this code:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

results in an UnboundLocalError.

This is because when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable. Consequently when the earlier print(x) attempts to print the uninitialized local variable and an error results.

In the example above you can access the outer scope variable by declaring it global:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
>>> foobar()
10

You can do a similar thing in a nested scope using the nonlocal keyword:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
>>> foo()
10
11

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接