我正在学习变量作用域,并在看到以下Python代码时查看了一些线程:
a = 1
b = 2
c = 3
def foo():
print a
print b
print c
c = c + 1
def main():
foo()
main()
打印出1
2
和UnBoundLocalError: local variable 'c' referenced before assignment
。当我把它翻译成Java
public class Test1 {
static int a = 1;
static int b = 2;
static int c = 3;
public static void foo()
{
System.out.println(a);
System.out.println(b);
System.out.println(c);
c = c + 1;
}
public static void main(String[] args)
{
foo();
}
}
打印出1
2
3
。我很确定我正确地翻译了它(如果它不是非常令人尴尬)。我的问题是为什么Python会出错,而Java却没有?是否与不同的范围或它们的解释和编译方式有关?
答案 0 :(得分:4)
Python没有变量声明。相反,它定义了一个规则,即您在函数中指定的任何名称都是该函数的局部变量。这意味着该行
c = c + 1
foo
中的使c
成为局部变量,所以
print c
尝试打印未分配的局部变量并引发异常。
Java有变量声明。您的Java代码在c
之外声明main
并且不在内部重新声明它,因此Java知道c
是静态变量,并且程序可以正常工作。可以将Python代码更好地转换为Java
public class Test1 {
static int a = 1;
static int b = 2;
static int c = 3;
public static void foo() {
int c; // Now c is local, like in the Python
System.out.println(a);
System.out.println(b);
System.out.println(c);
c = c + 1;
}
public static void main(String[] args) {
foo();
}
}
答案 1 :(得分:3)
你有这种不理解和惊讶,就像许多人学习Python一样(在stackoverflow.com上用“引用之前的引用”表达式进行研究),这是由于文档有时编写得很糟糕。
此错误的解释如下:
如果名称绑定操作发生在代码块中的任何位置,则全部 块中名称的使用被视为对的引用 当前块。在a中使用名称时,这可能会导致错误 在绑定之前阻止。这条规则很微妙。 Python缺乏 声明并允许名称绑定操作发生在任何地方 在代码块中。代码块的局部变量可以是 通过扫描确定块的整个文本以进行名称绑定 操作
在我看来,这个摘录严重表达了执行代码时执行的操作:
说“可以通过扫描确定”是欺骗性的,它给人的印象是这次扫描是可选的。
虽然我从来没有读过任何关于这一点的内容,这些内容可以证实我的观点,但我个人认为:
- 事实上,此扫描 IS 始终执行,这不是一个选项
- 更重要的是,这个扫描是在之前完成对块正在定义的可调用对象的任何调用
确实,首先要了解一个重要的概念:
必须认识到“定义”一词含糊不清,因为它可以通过两种方式来理解:
1 / definition =脚本中的代码块,用于“定义”某些内容
2 / definition =在执行脚本以创建定义的可调用对象时执行此代码块
我将这些论文作为基础:
块是一个 Python程序文本,它作为一个单元执行。 以下是块:模块,函数体和类 定义
函数定义定义用户定义的函数对象(...)
函数定义[sense 1]是可执行语句。它的执行绑定 当前本地命名空间中的函数名称到函数对象 (...)
函数定义[sense 2]不执行函数体; 只有在调用函数时才会执行此操作。http://docs.python.org/2/reference/compound_stmts.html#function-definitions
A function definition defines a user-defined function object
:这样一个美丽的重言式!这句话解释了什么。我认为分析以下内容会更有用:
[sense 1]那里,“定义”是指“定义”的代码块(=文本)
[sens 2]那里,“定义”意味着“执行定义代码块”;文本(定义意义1)不执行任何内容,它被动地作为文本......
您看到名称“定义”含糊不清,而且文档有时写得很糟糕......
最后一个提取关注函数定义,但概念显然可以扩展到类,其他可调用对象。类也由代码块定义,然后它们也存在这两个步骤:定义(意义2 =执行定义代码块)然后调用。
所以我的主张是,我的成立是为了认为在可调用对象中扫描标识符并确定其范围是在执行代码块[=定义感1]时执行的,此执行是所谓的“定义”[sense 2]也是如此 这就是我想指出的关键点。
PS:在doc的上述摘录中使用术语“变量”是令人遗憾的,因为“变量”在Python中使用时是另一个非常模糊的术语。
令人遗憾的证据是,OP提出了他的问题,比较Java中发生的事情以及Python中发生的事情
如果基本官方文档中的某个地方有一个可靠的解释,即在Python中,编码器无法访问充当“内容可以改变的内存块”的实体,这种混淆应该更少发生。
但这是另一个故事
答案 2 :(得分:1)
Python检查变量的局部范围,如果它未在本地范围内声明或引用,则会搜索更高的范围。通过在函数中使用c=c+1
,Python在本地范围内看到c
,并在尝试打印时抛出错误,因为它未声明。如果删除c=c+1
,则应打印c。要获得您期望的行为,请将global c
放入您的函数中。
注意: 通常使用全局变量不是一个好主意,因此pythonic替换可以将变量作为函数的参数传递,或者如果你正在做什么最终适合对类进行变量 self 。
e.g。
class myclass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
def count(self):
print self.a
print self.b
print self.c
self.c = self.c + 1
def main():
thing = myclass()
thing.count()
thing.count()
main()
给出
nero@ubuntu:~/so$ python -i so.py
1
2
3
1
2
4
>>>
答案 3 :(得分:1)
我认为混淆在于虽然Python是一种解释语言,但它确实分析了整个功能范围。参见示例:
>>> a=1;b=2;c=3
>>> def foo():
... print a, b, c #c refers to the c in the outer scope
...
>>> foo()
1 2 3
>>>
>>> def foo():
... print a, b, c #c refers to the local c defined later
... c = 2
...
>>> foo()
1 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'c' referenced before assignment
>>>
对于Python范围规则,您可以参考LEGB