iPython调试器引发`NameError:name ... not defined`

时间:2018-07-04 00:15:37

标签: python ipython pdb nameerror

我无法理解此Python调试程序会话中引发的以下异常:

(Pdb) p [move for move in move_values if move[0] == max_value]
*** NameError: name 'max_value' is not defined
(Pdb) [move for move in move_values]
[(0.5, (0, 0)), (0.5, (0, 1)), (0.5, (0, 2)), (0.5, (1, 0)), (0.5, (1, 1)), (0.5, (1, 2)), (0.5, (2, 0)), (0.5, (2, 1)), (0.5, (2, 2))]
(Pdb) max_value
0.5
(Pdb) (0.5, (0, 2))[0] == max_value
True
(Pdb) [move for move in move_values if move[0] == 0.5]
[(0.5, (0, 0)), (0.5, (0, 1)), (0.5, (0, 2)), (0.5, (1, 0)), (0.5, (1, 1)), (0.5, (1, 2)), (0.5, (2, 0)), (0.5, (2, 1)), (0.5, (2, 2))]
(Pdb) [move for move in move_values if move[0] == max_value]
*** NameError: name 'max_value' is not defined

为什么有时会告诉我max_value没有定义,而其他时候却没有?

顺便说一下,这是调试器启动之前的代码:

max_value = max(move_values)[0]
best_moves = [move for move in move_values if move[0] == max_value]
import pdb; pdb.set_trace()

我正在使用在PyCharm中运行的Python 3.6。

已修改的更新:

经过更多测试后,当我从iPython REPL或PyCharm中执行以下操作时,在pdb会话中的列表推导中似乎看不到局部变量:

$ ipython
Python 3.6.5 | packaged by conda-forge | (default, Apr  6 2018, 13:44:09) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import pdb; pdb.set_trace()
--Call--
> /Users/billtubbs/anaconda/envs/py36/lib/python3.6/site-packages/IPython/core/displayhook.py(247)__call__()
-> def __call__(self, result=None):
(Pdb) x = 1; [x for i in range(3)]
*** NameError: name 'x' is not defined

但是在常规的Python REPL中,它可以工作:

$ python
Python 3.6.5 | packaged by conda-forge | (default, Apr  6 2018, 13:44:09) 
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb; pdb.set_trace()
--Return--
> <stdin>(1)<module>()->None
(Pdb) x = 1; [x for i in range(3)]
[1, 1, 1]

我在上面使用版本3.4、3.5、3.6进行了测试,因此它似乎与版本无关。

更新2

请注意,上述测试(“修正的更新”)存在问题,因为它在交互式REPL中使用了import pdb; pdb.set_trace()

此外,最初的问题不仅限于iPython。

有关此处发生的情况的详细说明,请参见下面的answer by user2357112

对不起,如果造成任何混乱!

2 个答案:

答案 0 :(得分:3)

一个可能的解决方案/变通方法是运行

 globals().update(locals())

在 (i)pdb 中运行列表推导式之前。

答案 1 :(得分:1)

您在这里有两个核心问题。首先是(当在IPython中以交互方式调用pdb.set_trace()时)是在调试IPython的内脏,而不是所需的作用域。第二个原因是列表理解作用域规则与无法静态确定封闭范围中存在的变量(例如在调试器或class bodies中)的情况下相互作用不良。

第一个问题几乎只在向IPython交互式提示中键入pdb.set_trace()时发生,这不是一件非常有用的事情,因此,避免该问题的最简单方法就是不这样做。如果仍然要执行此操作,则可以多次输入r命令,直到pdb指出您已脱离IPython的胆量。 (不要超调,否则您将陷入IPython胆量的不同部分。)

第二个问题是,根深蒂固的语言设计决策在本质上是不可避免的相互作用。不幸的是,它不太可能消失。调试器中的列表推导仅在全局范围内起作用,而在调试函数时不起作用。如果要在调试功能时构建列表,最简单的方法可能是使用interact命令并编写一个for循环。


这是各种效果的完整组合。

  1. pdb.set_trace()在下一个 trace事件上触发pdb,而不是在调用pdb.set_trace()的时刻触发。

pdb和其他Python调试器使用的trace function机制仅在某些特定事件上触发,不幸的是“设置跟踪函数时”不是这些事件之一。通常,下一个事件要么是下一行的'line'事件,要么是当前代码对象执行结束的'return'事件,但这不是这里发生的情况。

  1. IPython设置了displayhook自定义表达式语句处理。

Python用于显示表达式语句结果的机制是sys.displayhook。当您在交互式提示下执行1+2时:

>>> 1+2
3

sys.displayhook是打印3而不是丢弃它的内容。它还设置_。当表达式语句的结果为None时,例如表达式pdb.set_trace()时,sys.displayhook不会执行任何操作,但仍会被调用。

IPython将sys.displayhook替换为其自己的自定义处理程序,该处理程序负责打印Out[n]:内容,设置Out记录中的条目,调用IPython自定义漂亮打印以及所有其他IPython便利。就我们的目的而言,重要的是IPython的displayhook是用Python编写的,因此下一个跟踪事件是displayhook的'call'事件。

pdb开始调试在IPython的displayhook内部

In [1]: import pdb; pdb.set_trace()
--Call--
> /Users/billtubbs/anaconda/envs/py36/lib/python3.6/site-packages/IPython/core/displayhook.py(247)__call__()
-> def __call__(self, result=None):
  1. 列表理解创建了一个新范围。

人们不喜欢列表理解如何将循环变量泄漏到Python 2中的包含作用域中,因此列表理解在Python 3中获得了自己的作用域。

  1. pdb使用eval,它与闭包变量的交互作用很差。

Python的闭包变量机制依赖于静态范围分析,该范围与eval的工作方式完全不兼容。因此,在eval内部创建的新作用域无法访问闭包变量;他们只能访问全局变量。


将所有内容放在一起,最终在IPython中调试IPython displayhook,而不是在其中运行交互式代码的范围。由于您位于IPython的displayhook中,因此您的x = 1分配进入了displayhook的本地。后续的列表理解将需要访问displayhook的本地变量才能访问x,但这将是列表理解的闭包变量,不适用于eval

在IPython之外,sys.displayhook用C编写,因此pdb无法输入它,并且没有'call'事件。您最终将调试要调试的范围。由于您处于全局范围内,因此x = 1进入全局范围,并且列表理解可以访问它。

如果您在调试任何普通函数时尝试运行x = 1; [x for i in range(3)],将会看到相同的效果。