由于某些原因,如果mutable被就地修改(或者根本没有被修改),Python就能够在内部函数的范围内访问mutable。
同样不适用于不可变项,除非它们被声明为nonlocal
,请参阅f7
和f8
。
使用任何不会就地修改变量的运算符修改mutable会导致UnboundLocalError
异常。
但是,我在Python文档中找不到任何特定于此行为的内容(或者我无法识别此信息)。
我一直在看:
https://docs.python.org/3.6/tutorial/classes.html#python-scopes-and-namespaces https://docs.python.org/3.6/reference/executionmodel.html#resolution-of-names
对我来说有点意义的是:
在代码块中使用名称时,使用最近的封闭范围解析它。代码块可见的所有此类范围的集合称为块的环境。 [...]
如果名称绑定操作发生在代码块中的任何位置,则块中名称的所有使用都将被视为对当前块的引用。在绑定之前在块中使用名称时,这可能会导致错误。这条规则很微妙。 Python缺少声明,并允许在代码块中的任何位置进行名称绑定操作。可以通过扫描块的整个文本来确定代码块的局部变量以进行名称绑定操作。
我认为这解释了为什么f1
和f3
有效,而f2
和f4
没有,但我仍然感到困惑为什么f6
不起作用 - 在c[0] = c[0]
之前使用c = c
之后。
所以我的问题是:
为什么f6
不起作用?后来使用c = c
某种方式"阴影"外部范围?
如果是,文档中描述了这种行为在哪里?
如果您能够通过参考行为的官方文档来支持您的答案,我将非常感激。
以下是重现我的发现的一些功能:
import inspect
def f1():
c = [0]
def g():
print(inspect.currentframe().f_code.co_freevars) # ('c',)
c[0] = c[0]
return c
return g
f1()()
def f2():
c = [0]
def g():
print(inspect.currentframe().f_code.co_freevars) # ()
c = c # UnboundLocalError: local variable 'c' referenced before assignment
return c
return g
# f2()()
def f3():
c = [0]
def g():
print(inspect.currentframe().f_code.co_freevars) # ('c',)
c.append(1)
return c
return g
f3()()
def f4():
c = [0]
def g():
print(inspect.currentframe().f_code.co_freevars) # ()
c = c[0] # UnboundLocalError: local variable 'c' referenced before assignment
return c
return g
# f4()()
def f5():
c = [0]
def g():
nonlocal c
print(inspect.currentframe().f_code.co_freevars) # ('c',)
c = c
return c
return g
f5()()
def f6():
c = [0]
def g():
print(inspect.currentframe().f_code.co_freevars) # ()
c[0] = c[0] # UnboundLocalError: local variable 'c' referenced before assignment
c = c
return c
return g
f6()()
def f7():
c = 1
def g():
print(inspect.currentframe().f_code.co_freevars) # ()
c = c # UnboundLocalError: local variable 'c' referenced before assignment
return c
return g
# f7()()
def f8():
c = 1
def g():
nonlocal c
print(inspect.currentframe().f_code.co_freevars) # ('c',)
c = c
return c
return g
f8()()
答案 0 :(得分:0)
您似乎忽略了所引用文档的一部分。这是更加强调:
如果名称绑定操作在代码块中的任何位置发生,则块中名称的所有使用都将被视为对当前块的引用。
在您的示例中,“代码块”是内部函数,“名称绑定操作”是c = c
赋值。赋值是无操作并不重要。当您在函数中的任何位置对名称c
进行分配时(无论分配了什么值),编译器将注意到c
是局部变量,所以所有 c
的使用(即使是之前发生的那些)将被视为本地参考。