11
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

4 回答 4

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将决定是全局查找还是局部查找变量,直到执行时间。

于 2013-09-05T23:06:16.687 回答
6

如Python 执行模型文档中所述:

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

这是语言的规则。就是这样。:D

于 2013-09-05T23:09:09.033 回答
1

当 python 编译为字节码(生成*.pyc文件)* 因为do_something = lambda: 'did nothing'你的函数中有一行do_something现在被视为局部变量,即使控制流没有将解释器带到那里。

这出乎意料的主要原因是:

  1. 与普遍看法相反,Python 是编译的

  2. 这是不直观的。

从根本上说,我认为只有在实施糟糕的设计时才会出现问题。当您do_something从一个函数中重新分配时,您正在使用全局范围 - 这很少是一个好主意。

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

于 2013-09-05T22:53:06.830 回答
0

是的,在 Python 3 中也是如此。在大多数情况下,这是可取的,即使不是完全直观的行为。也许你必须是荷兰人。很多人可能熟悉提升(通过 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

这行得通,我们被告知

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

但是没有locals()给我们所有的本地名称吗?显然不是。事实上,尽管有前面的声明,Python 暗示本地符号表可以在builtin的描述中locals()改变:

更新并返回表示当前[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?

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

于 2013-09-06T01:48:02.363 回答