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

Posted

技术标签:

【中文标题】iPython 调试器引发`NameError: name ... is not defined`【英文标题】:iPython debugger raises `NameError: name ... is not defined` 【发布时间】:2018-12-12 08:11:17 【问题描述】:

我无法理解此 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

请注意,上面的测试('AMENDED UPDATE')是有问题的,因为它在交互式 REPL 中使用了import pdb; pdb.set_trace()

另外,原来的问题不仅限于 iPython。

请参阅下面的answer by user2357112,以全面了解此处发生的情况。

很抱歉,如果我造成任何混乱!

【问题讨论】:

您可以尝试运行globals()locals() 来查看您的作用域中定义了哪些变量吗? globals()locals() 的输出是 here 我发现 this issue 从 2014 年开始在 Python 错误跟踪器中,状态为“已关闭”,解决方案“无法修复”,无论这意味着什么。 这听起来像是 iPython 和/或 Pycharm 与 Python 的接口中的一个错误。我会添加这些标签并删除名称错误。使用原始 Python 的另一个原因! 这个问题是在 2014 年 here 提出的,但似乎还没有解决方案...... 【参考方案1】:

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

第一个问题几乎只发生在在 IPython 交互式提示符中输入 pdb.set_trace() 时,这不是一件非常有用的事情,因此避免该问题的最简单方法就是不这样做。如果你还是想这样做,你可以输入r 命令几次,直到 pdb 说你已经没有 IPython 的胆量了。 (不要过头,否则你会陷入 IPython 胆量的不同部分。)

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


这里是完整的效果组合。

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

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

    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):
    列表推导式创建一个新范围。

人们不喜欢列表推导如何将循环变量泄漏到 Python 2 中的包含范围中,因此列表推导在 Python 3 中拥有自己的范围。

    pdb 使用eval,它与闭包变量的交互非常糟糕。

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


总而言之,在 IPython 中,您最终调试的是 IPython 显示钩子,而不是运行交互式代码的范围。由于您位于 IPython 的显示钩子中,因此您的 x = 1 分配进入显示钩子的本地。随后的列表解析需要访问 displayhook 的本地变量才能访问 x,但这将是列表解析的闭包变量,不适用于 eval

在 IPython 之外,sys.displayhook 是用 C 编写的,所以 pdb 无法进入它,并且它没有 'call' 事件。您最终调试了您打算调试的范围。由于您在全局范围内,x = 1 进入全局范围,列表推导可以访问它。

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

【讨论】:

请注意,最初的问题是在脚本或 Jupyter 笔记本中调用 set_trace 时发生的。只有在我(诚然天真的)尝试重新创建错误时,我才在 REPL 中使用了pdb.set_trace。无论如何,你可能是对的。今天我只能使用 set_trace 在 REPL 中重新创建这个错误,这不是一个真正的问题。谢谢。 实际上我仍然可以在脚本中重新创建此 NameError 。想让我更新问题以说明如何操作? @Bill:不完全是。 x = 1 在您正在调试的任何范围内设置一个名称。如果该范围是非全局范围,则列表推导无法访问它。 @Bill:我想说的是理解和基因表达是这种情况更有问题的一面。无论您是否从调试提示符执行分配,它们都会导致调试器出现问题,并且它们比分配更容易替换。此外,[i for i in range(x)] 不会引发错误,因为 range(x) 在理解范围之外进行评估。这有点奇怪,但它对于 genexps 中的早期错误检测很有用,并且理解共享行为以保持一致性。【参考方案2】:

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

 globals().update(locals())

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

【讨论】:

不错的解决方案!这也解决了一个类似的问题,即无法访问IPython.embed() 会话定义的函数。 github.com/ipython/ipython/issues/136

以上是关于iPython 调试器引发`NameError: name ... is not defined`的主要内容,如果未能解决你的问题,请参考以下文章

怎样将AI paddle 进行移植使用,paddle项目复制到pycharm中,sciview,NameError: name ‘get_ipython‘ is not defined

调用异步任务芹菜时引发异常:“NameError:未定义全局名称*”

NameError:名称“FaceDetector”未定义

Ipython 笔记本中的 pyspark 引发 Py4JNetworkError

Ipython Numpy pandas

将 Python 脚本退出到 IPython 命令行以进行调试