使用检查模块显示在堆栈框架中的未绑定名称

Unbound name showing up in stack frame using inspect module

我最近 运行 遇到了一个很难追踪的错误。我不小心将 class 名称重新用作变量(请参阅下面的代码),因此当我尝试调用 class 时,我(可以理解)得到了一个错误。很难追踪的原因是我的调试器 (Wing IDE 5.1.10) 会在调试探针中成功执行该行,但是当我尝试 运行 解释器中的同一行时它出错了。在进一步调查中,我发现当我使用检查模块检查帧数据时,名称仍然显示为绑定到我的 class 的全局变量。因此,我对在我的框架中明确定义和绑定的名称收到 UnboundLocalError 感到困惑。

这重现了问题:

import inspect

class MyClass(object):
    def __init__(self):
        print "MyClass init() method called successfully"

def newscope():

    #MyClass is not in the current frame's locals:
    assert 'MyClass' not in inspect.currentframe().f_locals.keys()

    #MyClass is in the current frame's globals and can be called successfully:
    class_object = inspect.currentframe().f_globals['MyClass']
    print class_object
    class_object()

    #But, calling MyClass by name results in UnboundLocalError: local
    #variable 'MyClass' referenced before assignment:
    print MyClass

    #Strangely, if at this point I go into the debug probe and run the same
    #line (print MyClass) it executes successfully, printing 
    #"<class '__main__.MyClass'>"

    #Re-assigning the name MyClass is what causes the UnboundLocalError:
    MyClass = 5

if __name__ == '__main__':
    newscope()

结果:

<class '__main__.MyClass'>
MyClass init() method called successfully
Traceback (most recent call last):
  Python Shell, prompt 1, line 29
  Python Shell, prompt 1, line 19
UnboundLocalError: local variable 'MyClass' referenced before assignment

再次,我明白为什么我会收到 UnboundLocalError。我不明白的是,为什么 inspect 模块仍将名称显示为绑定到 class 对象,而显然情况并非如此。我是不是遗漏了什么,或者这是 inspect 模块中的错误?

我 运行宁 python 2.7.11.

首先,关于例外情况,我认为您的 IDE 不符合 python 规范:

A scope defines the visibility of a name within a block. If a local variable is defined in a block, its scope includes that block.

[...]

If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.

[...]

When a name is not found at all, a NameError exception is raised. If the name refers to a local variable that has not been bound, a UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.

https://docs.python.org/2.7/reference/executionmodel.html#naming-and-binding

因此,我知道整个块都被解析了,它找到了你的变量,并被添加到本地范围,但在赋值之前,它被认为是一个 自由变量

编辑

关于inspect,我认为它列出了本地名称空间中的绑定变量,因此,您看不到您的变量。这很合乎逻辑:如果键 'MyClass' 尚未绑定,您会给它什么值?

实际上,您应该使用 inspect.currentframe().f_code.co_varnames 来获得您想要的 ;)

import inspect
from pprint import pprint

class MyClass(object):
        def __init__(self):
                print("MyClass init() method called successfully")

def newscope():
        pprint(inspect.currentframe().f_code.co_varnames)
        print("----------")
        pprint(inspect.currentframe().f_locals)
        print("----------")
        pprint(inspect.currentframe().f_globals)
        print("----------")
        try:
                pprint(MyClass)
        except Exception as e:
                print(e)
        MyClass = 5
        pprint(inspect.currentframe().f_locals)
        print("----------")
        pprint(inspect.currentframe().f_globals)
        print("----------")

if __name__ == '__main__':
        newscope()

你得到:

('MyClass', 'e')
----------
{}
----------
{'MyClass': <class '__main__.MyClass'>,
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': 'test.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f2fa3901160>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from '/usr/lib/python3.5/inspect.py'>,
 'newscope': <function newscope at 0x7f2fa39b8f28>,
 'pprint': <function pprint at 0x7f2fa1fe66a8>}
----------
local variable 'MyClass' referenced before assignment
{'MyClass': 5}
----------
{'MyClass': <class '__main__.MyClass'>,
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': 'test.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f2fa3901160>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from '/usr/lib/python3.5/inspect.py'>,
 'newscope': <function newscope at 0x7f2fa39b8f28>,
 'pprint': <function pprint at 0x7f2fa1fe66a8>}
----------

删除你的变量

import inspect
from pprint import pprint
class MyClass(object):
        def __init__(self):
                print("MyClass init() method called successfully")

def newscope():
        pprint(inspect.currentframe().f_code.co_varnames)
        print("----------")
        pprint(inspect.currentframe().f_locals)
        print("----------")
        pprint(inspect.currentframe().f_globals)
        print("----------")
        try:
                pprint(MyClass)
        except Exception as e:
                print(e)
        # MyClass = 5
        pprint(inspect.currentframe().f_locals)
        print("----------")
        pprint(inspect.currentframe().f_globals)
        print("----------")

if __name__ == '__main__':
        newscope()

你得到:

('e',)
----------
{}
----------
{'MyClass': <class '__main__.MyClass'>,
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': 'test.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fc6d3fcb160>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from '/usr/lib/python3.5/inspect.py'>,
 'newscope': <function newscope at 0x7fc6d4082f28>,
 'pprint': <function pprint at 0x7fc6d26b06a8>}
----------
<class '__main__.MyClass'>
{}
----------
{'MyClass': <class '__main__.MyClass'>,
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': 'test.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fc6d3fcb160>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from '/usr/lib/python3.5/inspect.py'>,
 'newscope': <function newscope at 0x7fc6d4082f28>,
 'pprint': <function pprint at 0x7fc6d26b06a8>}
----------

如果一个值被分配给函数内的变量,该变量将成为该函数内的局部变量。

从创建函数的那一刻起,即在第一次调用之前,该变量就被视为局部变量。 Python 实际上优化了对局部变量的访问并且不查找 locals() 字典,但是 "knows" 确切地找到每个局部变量的位置(参见 this answer about performance within a function)。

所以,这个赋值是在函数的末尾完成这一事实并没有什么不同。在你的函数 newscope 中,变量 MyClass 是一个局部变量。使用后分配给 MyClass 变量实际上是导致此示例中的 UnboundLocalError 的原因。

举个更简单的例子:

a = 4

def global_a_example():
    print a   # a is a global variable: prints 4

def local_a_example():
    a = 5
    print a   # a is a local variable: prints 5

def unbound_local_a_example():
    print a   # a is a local variable, but not initialized: raises UnboundLocalError
    a = 5

编辑: 解释为什么看起来变量已绑定

请注意,未绑定的局部变量不会出现在局部变量字典中。那不是因为他们不是本地人。这是因为它们不受约束。请参阅以下示例:

a = 1
b = 2

def f():
    assert 'a' in globals()    # of course
    assert 'a' not in locals() # local 'a' has not been initialized and has no value
    assert 'a' in f.__code__.co_varnames # 'a' is local nevertheless!
    assert 'b' not in f.__code__.co_varnames # 'b' is not local...

    # a != 1 test would raise and exception here because 'a' is local and uninitialized

    a = 10                     # initialize local 'a' (and store it in locals)

    assert 'a' in globals()    # it is still in globals, yes
    assert 'a' in locals()     # it is also in locals

    assert globals()['a'] == 1 # global 'a' has value 1
    assert locals()['a'] == 2  # local 'a' has value 2
    assert a == 10             # a is local 'a'!
    assert b == 2              # b is global 'b'

# but you don't even have to call f()!!!
# 'a' is already defined to be a local variable in f, see:

print f.__code__.co_varnames # prints ('a',)

因此,'a' 在写入之前不会绑定。它是 globals dict 中的关键,但这无关紧要。它没有从那个字典中使用,因为它被定义为本地的。