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

在Python中,if条件中的变量隐藏了全局范围,即使它们没有执行?,python,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() 即使if语句中的条件从未执行,函数是如何被重写的?这在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()

即使if语句中的条件从未执行,函数是如何被重写的?这在Python2.7中发生——在Python3中也一样吗?

当Python编译成字节码(生成
*.pyc
文件)*时,因为在函数中有一个
do\u something=lambda:'do nothing'
do\u something
现在被视为局部变量,即使控制流没有将解释器带到那里

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

  • 与普遍的看法相反

  • 这是不直观的

  • 从根本上说,我认为这只会成为一个问题,如果你正在实施糟糕的设计。当你从一个你正在使用全局作用域的函数中重新分配
    do_some
    时,这很少是一个好主意

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

    即使if语句中的条件从未执行,函数是如何被重写的

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

    这在Python2.7中发生——在Python3中是否相同

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


    函数内部的
    exec
    会将全局或局部查找变量的决定推迟到执行时间。

    如以下文档中所述:

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


    这是语言的规则。事情就是这样是的,在Python 3中也是一样的。在大多数情况下,这是可取的,如果不是完全直观的行为。许多人可能熟悉吊装(通过JavaScript普及?)。它也在Python中发生,只是Python没有定义
    undefined
    值,而是引发
    UnboundLocalError
    。比较:

    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
    
    这很有效,我们已经

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

    但是
    locals()
    不是给了我们所有的本地名称吗?显然不是。事实上,尽管有前面的语句,Python提示本地符号表可以在以下方面更改:

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

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


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

    请参见:Quote:“Python根据是否从函数内部为函数中的变量赋值,对函数中的变量进行不同的处理。”@flornquake是的,但我什么时候为该变量赋值的?这个变量是不是在从未执行过的代码中赋值的?@Buttons840,如果你在函数中给这个变量赋值,那么局部变量将成为全局变量的影子。我对这个世界的理解被动摇了…:(振作起来!事情并没有那么糟。为什么投票被否决了?也许它不应该花那么多时间在黑客
    exec
    解决方案上,但它明确表示不建议这样做,只有在正确解释了发生了什么之后,它才会进入这个问题。+1;不是因为我认为这是解决这个问题的一个好方法,而是一个坏的o但是因为我学到了一些东西。这里的exec导致最终返回被编译为
    LOAD\u NAME
    ,而不是
    LOAD\u GLOBAL
    。这个答案的好处是它正确地暗示了,即使在不像CPython那样编译成字节码的Python实现中,同样的事情也会发生。但是我你认为其他两个答案在编译方面的解释有助于理解事物,只要你记住它们只是在描述CPython。
    def do_something():
        print 'doing something...'
    
    def maybe_do_it(hesitant=False):
        result = do_something()
        print result
    
    maybe_do_it()
    
    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
    
    > // 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
    
    >>> def foo():
    ...   if not 'x' in locals():
    ...     x = 2
    ...   return x
    ... 
    >>> foo()
    2
    
    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?