4

当我看到以下 Python 代码时,我正在学习变量范围,并且正在查看一些线程:

a = 1
b = 2
c = 3

def foo():

    print a
    print b
    print c
    c = c + 1

def main():

    foo()

main()

打印出来1 2UnBoundLocalError: 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 不会?是否与不同的范围或解释和编译它们的方式有关?

4

4 回答 4

4

Python 没有变量声明。相反,它定义了一个规则,即您在函数中分配的任何名称都是该函数的局部变量。这意味着该行

c = c + 1

in生成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();
    }   
}
于 2013-07-13T03:13:44.937 回答
3

就像许多学习 Python 的人一样(在 stackoverflow.com 上使用“赋值前引用”表达式进行研究),我所包含的不理解和惊讶是由于文档有时写得不好。

这个错误的解释在这里:

如果名称绑定操作发生在代码块中的任何位置,则块中名称的所有使用都被视为对当前块的引用。如果在绑定之前在块中使用名称,这可能会导致错误。这个规则很微妙。Python 缺少声明,并允许名称绑定操作在代码块中的任何位置发生。代码块的局部变量可以通过扫描块的整个文本以进行名称绑定操作来确定。

http://docs.python.org/2/reference/executionmodel.html

在我看来,这段摘录糟糕地表达了执行代码时执行的操作:
说“可以通过扫描确定”是欺骗性的,它给人的印象是这种扫描是可选的。

虽然我从来没有读过任何可以证实我观点的关于​​这一点的内容,但我个人认为:
- 事实上,这个扫描总是执行的,这不是一个选项
- 更重要的是,这个扫描是在调用任何可调用对象之前完成的该块正在定义

.

确实,首先要了解一个重要的概念:

可调用对象的定义在对象被调用之前完成

必须意识到术语“定义”是模棱两可的,因为它可以通过两种方式来理解:
1/定义=脚本中“定义”某物
的代码块2/定义=在执行时执行此代码块创建定义的可调用对象的脚本

我基于以下断言:

是作为一个单元执行的一段Python 程序文本以下是块:模块、函数体和类定义。

http://docs.python.org/2/reference/executionmodel.html

.

函数定义定义用户定义的函数对象 (...)
函数定义 [意义 1] 是可执行语句。它的执行将当前局部命名空间中的函数名绑定到一个函数对象(...)
函数定义[sense 2]不执行函数体;this 仅在调用函数时执行。

http://docs.python.org/2/reference/compound_stmts.html#function-definitions

.

A function definition defines a user-defined function object: 好漂亮的重言式!这句话说明了什么。我认为分析以下内容更有用:
[sense 1] 那里,“definition”是指“定义的代码块(=文本)”
[sens 2],“definition”是指“定义代码块的执行”;文本(定义意义 1)不执行任何操作,它作为文本被动地撒谎......

您会看到名称“定义”是模棱两可的,而且文档有时写得不好......

最后一个提取关注函数定义,但这些概念显然可以扩展到类,其他可调用对象。类也由代码块定义,然后它们也存在这两个步骤:定义(意义 2= 定义代码块的执行)然后调用。

.

所以我的主张是,我认为扫描可调用对象中的标识符并确定它们的范围是在执行代码块 [= 定义意义 1] 的那一刻执行的,这种执行被称为“定义” [意义 2] 也是。
这就是我想指出的关键点。

.

PS:在上述文档摘录中使用术语“变量”是令人遗憾的,因为“变量”在 Python 中使用时是另一个高度含糊的术语。
令人遗憾的证据是,OP 提出了他的问题,即比较 Java 中发生的事情和 Python 中发生的事情。
如果在基本的官方文档中某处有一个可靠的解释,即在 Python 中编码器无法访问充当“内容可以更改的内存块”的实体,那么这种混淆应该很少发生。
但那是另一个故事

于 2013-07-13T05:17:04.193 回答
1

Python 检查变量的局部作用域,如果它没有在局部作用域中声明或引用,它会搜索更高的作用域。通过c=c+1在函数中使用 Pythonc在本地范围内看到并在您尝试打印时抛出错误,因为它没有被声明。如果您删除c=c+1它应该打印 c。要获得您期望放入global c函数中的行为。

注意:使用全局变量通常不是一个好主意,因此 pythonic 的替代方法可能是将变量作为函数的参数传递,或者如果你所做的最终适合类,则使变量self

例如

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
>>> 
于 2013-07-13T03:10:40.257 回答
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

于 2013-07-13T03:44:07.603 回答