我怎么知道引发异常的确切命令?

Posted

技术标签:

【中文标题】我怎么知道引发异常的确切命令?【英文标题】:How do I know the exact command that raised exception? 【发布时间】:2019-08-25 06:58:15 【问题描述】:

关于 SO 的相关问题(今天早些时候由我本人提出):Why does error traceback show edited script instead of what actually ran? 现在我知道为什么会发生这种情况,那么我现在想如何处理它。

我看到一些问题,如 How do I debug efficiently with spyder in Python? 和 How do I print debug messages in the Google Chrome javascript Console? 很受欢迎,所以我想询问调试实践是一个话题,对吧?

背景

我编写了一个在 n 行引发异常的脚本,从终端运行它,在脚本仍在运行时在中间添加一行,然后保存修改后的文件。所以脚本文件在解释器运行时被修改。特别是会引发异常的行的行号已经改变。 Python 解释器的错误回溯报告向我显示了脚本“修改”版本的第 n 行,而不是实际的“运行”版本。

小例子

假设我运行一个脚本:

import time

time.sleep(5)
raise Exception

当解释器卡在time.sleep(5) 时,我在后面添加了一行。

所以现在我有:

import time

time.sleep(5)
print("Hello World")
raise Exception

然后解释器从睡眠中醒来,执行下一条命令raise Exception,程序以以下回溯终止。

回溯(最近一次通话最后一次): 中的文件“test/minimal_error.py”,第 4 行 打印(“你好世界”) 例外

所以它正确地报告了行号(来自 original 脚本,所以如果我们只有修改过的脚本实际上是无用的)和错误消息(“异常”)。但它显示了实际上引发错误的完全错误的代码行;如果它有任何帮助,应该显示raise Exception,而不是print("Hello World"),它甚至没有被解释器执行。

为什么这很重要

在实际实践中,我实现了程序的一个部分,运行它以查看该部分是否运行良好,当它仍在运行时,我继续执行下一个我必须实现的事情。当脚本抛出错误时,我必须找出导致错误的实际代码行。我通常只是阅读错误信息并尝试推断导致它的原始代码。

有时不容易猜到,所以我将脚本复制到剪贴板并通过在运行脚本后撤消我编写的内容来回滚代码,检查导致错误的行,然后从剪贴板粘贴回来。有时这很烦人,因为当我运行它时,并不总是能记住脚本的确切状态。 (“我是否需要撤消更多操作才能回滚?或者这正是我运行的脚本?”)

有时脚本会运行超过 10 分钟,甚至是一个小时,然后才会引发异常。在这种情况下,“通过撤消回滚”实际上是不可能的。有时我什至不知道脚本在实际运行之前会运行多长时间。我显然不能在脚本终止之前坐下来保持脚本不被修改。

问题

通过什么做法我可以正确追踪导致异常的命令?

一个假设的解决方案是每次我想运行脚本时都将它复制到一个新文件中,运行复制的版本,然后继续编辑原始版本。但是我觉得这太麻烦了,不能每十分钟做一次,每当我需要运行一个脚本来看看它是否运行良好时。

另一种方法是每次我想运行它时都git-commit,这样我可以在需要的时候回来查看原始版本,但这会使提交历史非常脏,所以我认为这比另一个更糟糕。

我也尝试了python -m pdb -m script.py,但它显示了相同的“行 n 的修改版本”,就像普通的回溯一样。

那么有什么实用的解决方案可以让我练习,比如每十分钟练习一次吗?

【问题讨论】:

您不会从您发布的代码中获得该回溯,至少不是来自新导入的模块,或者如果您将其作为脚本运行。引发异常的行是raise Exception @chepner,我从python test.py 等终端运行脚本(没有print(...) 行),然后在解释器卡在time.sleep(5) 时添加print(...) 行,保存脚本,解释器完成睡眠并到达raise,因此引发异常,并且回溯显示带有print(...) 的行。我想我在“最小示例”中说清楚了,但如果我没有说清楚,我很抱歉。你觉得我怎样才能说得更清楚? 我很确定实际的解决方案是“不要这样做”。不要修改正在运行的代码。 你可以写一些东西来自动化你假设的解决方案。 mktemp 可能有用。 【参考方案1】:

不必每次运行脚本时都提交,只需使用 git stash,这样您就不会将 dirty commits 添加到您的历史记录中。

因此,在您运行脚本之前,git stash 您的本地更改,检查错误,然后git stash pop

阅读更多关于 git stash here.

这个方案假设运行的脚本在当前分支的HEAD


如果上述条件不适用,另一种解决方案是创建一个任意分支,调用它(运行脚本),git stash 您尚未提交的本地更改,checkout 到这个新分支,@ 987654328@ 并运行脚本。然后结帐回到您原来的分支,重新应用存储并恢复您的工作。

您可以简单地编写一个 bash 脚本文件来自动执行此过程,如下所示

git stash
git checkout -b running-script # potential param
git stash apply stash
RUN script # replace with the actual command to run the script in the background
git checkout original-branch # potential param
git stash apply stash

您可以将运行脚本和原始分支作为参数传递给 bash 文件。

【讨论】:

【参考方案2】:

@chepner 的评论有效:

我很确定实际的解决方案是“不要这样做”。不要修改运行代码。

作为一种相对简单的解决方法,您可以使用 bash 脚本(或在 bash 不可用时在您使用的任何环境中使用类似的脚本方法)来完成此操作。

对于 bash,可以使用如下脚本。它将文件名作为参数并使用date 创建一个唯一的临时文件名,然后将文件复制到它并执行它。通过这种方式,您始终拥有运行代码的静态副本,并且您可以使用别名来简化使用:

filename=$1

# extract file name and extension
extension="$filename##*."
filename="$filename%.*"

# create a unique temporary name (using date)
today=`date +%Y-%m-%d-%H:%M:%S` # or whatever pattern you desire
newname="$filename-$today.$extension"

# copy and run the python script
cp $1 $newname
echo "Executing from $newname..."
/path/to/python $newname

# clean it up when done, if you care to
rm $newname

如果需要,您可以将其别名为 python,这样您就不必考虑这样做了,在您的 .bashrc.bash_aliases 中使用类似的内容:

alias python="source path/to/copy_execute.sh"

虽然给它起一个不同的名字可能会更好,比如

alias mypy="source path/to/copy_execute.sh"

然后,您可以使用mypy myscript.py 运行您的脚本、修改和运行更多内容,而您将永远不会编辑当前正在执行的代码。

一个缺点是,虽然此脚本在运行完成后会清理并删除文件,但它会创建大量临时文件,这些文件将在运行时出现。为了解决这个问题,您可以随时复制到/tmp 或临时文件不会妨碍的其他地方。另一个问题是,对于您可能不想到处复制的大型代码库,这变得更加复杂。我会把那个留给你。

可以使用 powershell 或 cmd 为 Windows 设计类似的方法。

【讨论】:

【参考方案3】:

我可能会给出一个过于简单的答案,并且可能并不适用于所有场景。

使用 PyCharm

我通常使用需要几分钟到几小时才能完成的代码,我需要不断地运行它以查看它的性能,并在它运行时继续编码。如果失败,我会收到引发错误的原始行。

我还必须在无 GUI 的 Ubuntu 服务器中运行它,所以我每次都会这样做以接收正确的错误:

    Pycharm 中的代码。 在 PyCharm 中测试并继续编码。 (如果是,我会得到正确的错误 失败) 一旦我对性能感到满意,我会将其移至服务器并 再次运行它(我在这里也得到正确的错误)

【讨论】:

【参考方案4】:

我并不是说它会完全避免,但你可以减少这个错误。 如果您将所有逻辑编码在一个文件中,请停止这样做。

这里有一些推荐..

    将您的代码逻辑拆分为多个文件。例子..

    实用程序, 帮手, 型号, 组件, 火车, 测试, 功能

    让你的函数小到 10 行(如果可能的话)

    如果您使用的类不应超过 125 行 文件大小不应超过 150 行

现在,如果发生任何异常,那么它的回溯可能会传播到更多数量的文件中,我猜并非所有文件都被一次性修改以实施您的更改。 好消息是,如果您的异常是从您没有更改的文件开始的,那么很容易捕获该行并修复它,否则找到确切的行将是最小的努力。

如果您也在使用 git 并且您有 未提交,那么您还可以 比较修订版 以获取可能导致错误的确切代码。

希望这可以最大限度地减少您的问题。

【讨论】:

以上是关于我怎么知道引发异常的确切命令?的主要内容,如果未能解决你的问题,请参考以下文章

我怎么知道是啥引发了异常?

使用 numba 时引发异常

如何修复错误 命令引发异常:TypeError: 'Member' object is not iterable

discord.ext.commands.errors.CommandInvokeError:命令引发异常:KeyError:'link'

命令引发异常:NotFound: 404 Not Found (error code: 0): Interaction is unknown (你已经响应了交互)

机油测试引发未捕获的异常