在Python中,if条件中的变量是否隐藏全局范围,即使它们没有被执行?

时间:2013-09-05 22:47:20

标签: python

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    if hesitant:
        do_something = lambda: 'did nothing'
    result = do_something()
    print result

maybe_do_it()

此代码的结果是:

  File "scope_test.py", line 10, in <module>
    maybe_do_it()
  File "scope_test.py", line 7, in maybe_do_it
    result = do_something()
UnboundLocalError: local variable 'do_something' referenced before assignment

但是这段代码按照预期印刷了“做了些什么......”:

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    result = do_something()
    print result

maybe_do_it()

即使if语句中的条件从未执行过,该函数是如何被覆盖的?这种情况发生在Python 2.7中 - 在Python 3中是否相同?

4 个答案:

答案 0 :(得分:8)

  

即使if语句中的条件从未执行过,该函数是如何被覆盖的?

变量是本地变量还是全局变量的决定是在编译时进行的。如果函数中的任何地方都有变量赋值,那么它就是一个局部变量,无论赋值是否被执行过。

  

这在Python 2.7中发生 - 在python 3中是否相同?

顺便说一句,在Python 2中,您可以使用exec(不推荐)覆盖此行为:

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    if hesitant:
        exec "do_something = lambda: 'did nothing'"
    result = do_something()
    print result

maybe_do_it(False)    # doing something...
maybe_do_it(True)    # did nothing

松散地说,函数内的exec会推迟决定是全局查找变量还是本地查找执行时间。

答案 1 :(得分:6)

Python Execution Model的文档中所述:

  

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

这是一种语言规则。这就是它的方式。 :d

答案 2 :(得分:1)

当python编译为字节码(生成*.pyc文件)*因为函数do_something = lambda: 'did nothing'中有do_something行现在被视为局部变量,即使控制流也是如此没有翻译那里。

出乎意料的主要原因是:

  1. 与普遍看法相反,Python is compiled

  2. 这是不直观的。

  3. 从根本上说,如果你实施糟糕的设计,我认为这只会成为一个问题。当您从正在使用全局范围的函数中重新分配do_something时 - 这不是一个好主意。

    *正如已经指出的那样,这实际上并不适用于编译为字节码的Python(CPython) - 它实际上是该语言的一个特性。我的解释细节(用字节码表示)仅指CPython。

答案 3 :(得分:0)

是的,它在Python 3中是相同的。在大多数情况下,如果不是完全直观的行为,这是可取的。 Perhaps you have to be Dutch。许多人可能熟悉吊装(由JavaScript推广?)。它也发生在Python中,除了没有undefined值,Python只会引发UnboundLocalError。比较:

> // JavaScript example
> var x = 1;
> function foo() {
    if (!x) { // x is declared locally below, so locally x is undefined
      var x = 2;
    }
    return x;
  }
> foo();
2

>>> # Everything else is Python 3
>>> x = 1
>>> def foo():
...   if not x: # x is bound below, which constitutes declaring it as a local
...     x = 2
...   return x
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

到目前为止,Python一致,但有一个(令人不安的)解决方法:

>>> def foo():
...   if not 'x' in locals():
...     x = 2
...   return x
... 
>>> foo()
2

有效,我们been told

  

可以通过扫描块的整个文本来确定代码块的局部变量以进行名称绑定操作。

但是locals()没有给我们所有的本地名字?显然不是。实际上,尽管有前面的声明,但Python暗示本地符号表可以在the description of the locals() builtin中更改:

  

更新并返回代表当前 [emphasis mine]本地符号表的字典。

我曾经认为 current 这个词是指这些值,现在我认为它也指的是键。但最终我认为这意味着没有办法(缺少转储和解析框架的源代码)来枚举所有在本地声明的名称(这并不是说你不能使用try / except UnboundLocalError来确定特定名称是否是本地名称。)

def foo():
    # Some code, including bindings
    del x, y, z # or any other local names
    # From this point, is it programmatically knowable what names are local?

我认为,这是具有隐式声明的语言与具有显式声明的语言之间的根本区别。