Python: Referencing and assignment in inner functions
这一篇的内容在 Digest of Fluent Python: 7.4 Variable Scope Rules 和 Python: Variable scope in if-statement 都有讲到,但是 Effective Python 的 Item 15: Know How Closures Interact with Variable Scope 讲得特别好,特摘录如下。
When you reference a variable in an expression, the Python interpreter will traverse the scope to resolve the reference in this order:
- The current function’s scope
- Any enclosing scopes (like other containing functions)
- The scope of the module that contains the code (also called the global scope)
- The built-in scope (that contains functions like
len
andstr
)
If none of these places have a defined variable with the referenced name, then a NameError
exception is raised.
Assigning a value to a variable works differently. If the variable is already defined in the current scope, then it will just take on the new value. If the variable doesn’t exist in the current scope, then Python treats the assignment as a variable definition. The scope of the newly defined variable is the function that contains the assignment.
看三个例子:
def foo_1():
found = False
def bar():
found = True
bar()
print(found)
foo_1()
# Output: False
def foo_2():
found = False
def bar():
nonlocal found
found = True
bar()
print(found)
foo_2()
# Output: True
def foo_3():
found = [False]
def bar():
found[0] = True
bar()
print(found[0])
foo_3()
# Output: True
foo_1
里的bar
的found = True
是一句 assignment,但是found
在bar
的 scope 下找不到,所以变成了 variable definition,相当于内部外部各有一个found
,你内部的found
的赋值不会影响到外部的值。foo_2
这是最常见的解决foo_1
问题的写法,用nonlocal
,告诉 interpreter 不要在bar
的 scope 下去找found
。这么一来内外 reference 到的都是同一个found
。foo_3
是 python 2 里nonlocal
的 workaround,因为 python 2 里并没有nonlocal
。它的巧妙之处在于found[0] = True
这句并不是对found
的赋值。所以这里内外都是 reference 到同一个found
,相当于是改变了found
的内部状态,但是并没有 assign 一个新对象给found
。- 同理可知,你在 inner function 里修改外部对象的状态也不算是 assignment,比如下面的
foo_4
。
- 同理可知,你在 inner function 里修改外部对象的状态也不算是 assignment,比如下面的
class Found:
def __init__(self, boolean):
self.value = boolean
def foo_4():
found = Found(False)
def bar():
found.value = True
bar()
print(found.value)
foo_4()
# Output: True
Comments