Debug Console Limitations


Some code will work in unexpected ways in Wing's Debug Console due to how list comprehensions, generator expressions, and nested functions work in Python 3. This results in inability to evaluate some code when stopped at a breakpoint in a function or method.

Nested Function Scope

The most comonly noticed example is inability access variables in an enclosing scope when within a nested function. For example when the debugger is stopped on the line return 1 in the following code, typing self in the Debug Console raises a NameError:

class C:

  def m(self):
    def nested():
      return 1
    nested()

c = C()
c.m()

This is a result of how Python's compiler binds variables from the nested scope into nested functions. If the variable is not used in the nested function then it will not be defined there at all.

There is no work-around for this problem, other than moving up to the enclosing stack frame in the debugger and inspecting the variable there instead.

List Comprehensions and Generators

List comprehensions and generator expressions suffer from a related problem when used in the Debug Console. For an example, try stopping on print(foo) in the following code:

def x():
  from string import capwords
  foo = ['one two', 'three four']
  print(foo)

x()

Now typing the following list comprehension in the Debug Console will raise a NameError indicating that capwords is not defined:

y = [capwords(x) for x in foo]

This is because in Python 3 the list comprehension is implemented internally as a nested function and Python's compiler plays tricks to bind the necessary variables from the enclosing scope into the nested function. Even though capwords is defined in locals() the compiler does not use that when creating the code object for the list comprehension. Instead, it references the symbol table of the enclosing function which in the case of the Debug Console is (unavoidably) not x().

Generator expressions have the same problem:

y = (capwords(x) for x in foo)
x = list(y)

And so do nested functions, if defined within the Debug Console:

def f():
  capwords('test me')
f()

A possible work-around to use in some cases is to first load the locals into globals by typing the following in the Debug Console:

globals().update(locals())

However, this drastically alters program state in ways that may be destructive even if the original contents of globals() is restored after the evaluation.