在下面的代码中:
def f():
a = 'x'
def g():
print(a)
if a == 'x':
return True
return False
def h():
print(a)
def i():
a = a + a
return a
a = i()
return a
if g():
return h()
为什么a
可以在函数g
中访问,而不能在函数h
或i
中访问?你知道吗
我不想使用nonlocal
,因为我不想修改任何内部函数中的a
,但是我不明白为什么a
本身是不可访问的。你知道吗
Tags:
other answer对于解释出了什么问题非常有用。我添加了我自己的答案,试图解释问题背后的一些原因(即“为什么”而不是“什么”)。你知道吗
首先,您需要稍微了解一下Python的体系结构。我们经常将Python描述为一种“解释”语言,而不是像C这样的“编译”语言,但这并不是全部。虽然Python不直接编译为机器代码,但是当程序运行时,解释器不会在原始源代码上运行。相反,有一个中间步骤,源代码被编译成字节码。编译是在加载模块时自动进行的,因此您甚至可能不知道它(尽管您可能已经看到了编译器为缓存字节码而编写的
.pyc
文件)。你知道吗无论如何,回到您的作用域问题:Python的编译器使用字节码指令来告诉解释器访问局部变量,而不是用于访问全局变量(第三条不同的指令用于从封闭函数的作用域访问变量)。由于字节码是由编译器编写的,所以要使用的字节码指令需要在函数的编译时决定,而不是在调用函数时决定。指令的选择是很棘手的,但是对于这种模棱两可的代码:
对
print
调用的a
的访问是使用读取全局变量a
的字节码指令还是使用访问局部变量a
的指令?编译器无法提前知道bar
是否返回真值,因此没有可能的答案让函数在所有情况下都能工作。你知道吗为了避免歧义,Python的设计人员选择变量的作用域在每个函数中都应该是常量(这样编译器就可以选择一条字节码指令并继续使用它)。也就是说,像
a
这样的名称可以引用局部或全局(或闭包单元),但在任何给定函数中只能引用其中一个。你知道吗编译器默认使用局部变量(访问速度最快)作为函数代码中任何位置的赋值目标。由于内部函数与包含它们的函数同时编译,因此也可以在编译时检测到非本地查找并使用适当的指令。如果在局部作用域或封闭作用域中都找不到该名称,编译器将假定它是一个全局变量(现在还不需要定义)。
global
和nonlocal
语句允许您显式地告诉编译器使用特定的作用域(重写它自己选择的作用域)。你知道吗您可以使用标准库中的
dis
模块探索编译器在不同范围内处理变量查找的不同方式。dis
模块将字节码反汇编成更可读的格式。尝试调用dis.dis
函数,如下所示:关于如何解释
dis.dis
的输出的全部细节超出了这个答案的范围(没有双关语),但是需要寻找的主要内容是LOAD_...
字节码指令,这些指令将(a)
作为它们的目标。在上面的不同函数中,您将看到三个不同的LOAD_...
指令,对应于它们所读取的三种不同类型的作用域(每个函数都以相应的指令命名)。你知道吗简短回答:因为分配给
a
(通过写入a = a + a
和a = i()
),所以创建了局部变量。在赋值之前使用变量并不重要。你知道吗Python通过检查赋值来检查作用域。如果你在某处写了一个赋值,比如
a =
,a +=
,等等,不管你把它写在函数的什么地方,函数都会把a
看作一个局部变量。你知道吗如果你写:
即使在赋值给
a
之前访问a
,它仍将a
视为局部变量。Python在这里不做代码路径分析。你知道吗它在
f
中看到一个局部变量。如果调用f()
,则会出错,因为它会说在实际分配a
之前获取它。你知道吗如果变量不是本地定义的,Python将迭代地检查外部作用域,直到找到一个
a
。你知道吗如果在作用域中赋值给,则从外部作用域访问变量的唯一方法是使用
nonlocal
或global
(当然也可以将其作为参数传递)。你知道吗相关问题 更多 >
编程相关推荐