关于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
,程序终止以下回溯。
Traceback(最近一次调用最后一次): 在<module>中的文件“test / minimal_error.py”,第4行 打印(“Hello World”) 例外
所以它正确地报告行号(来自原始脚本,因此如果我们只有修改过的脚本实际上没用)和错误消息(“异常”)。但它显示了一个完全错误的代码行,实际上引发了错误;如果它有任何帮助,应该显示raise Exception
,而不是print("Hello World")
,甚至没有由翻译执行。
在实际操作中,我实现了程序的一部分,运行它以查看该部分是否正常运行,并且当它仍在运行时,我继续执行下一步我要实现的事情。当脚本抛出错误时,我必须找到导致错误的实际代码行。我通常只是阅读错误消息并尝试推断导致它的原始代码。
有时候猜测并不容易,所以我将脚本复制到剪贴板并通过撤消运行脚本后写的内容来复制代码,检查导致错误的行,然后从剪贴板粘贴回来。有时候这很烦人,因为在我运行它时并不总是能够记住脚本的确切状态。 (“我是否需要撤消更多以进行回滚?或者这是我运行的确切脚本吗?”)
有时脚本会运行超过10分钟,甚至在引发异常之前一小时。在这种情况下,“通过撤销回滚”实际上是不可能的。有时我甚至不知道脚本在实际运行之前会运行多长时间。我显然不能只是坐下来保持我的脚本在它终止之前不被修改。
通过什么实践,我可以正确地追踪导致异常的命令?
一个假设的解决方案是每次我想运行它时将脚本复制到一个新文件,运行复制的版本,并继续编辑原始文件。但是我认为,每当我需要运行一个脚本来查看它是否运行良好时,每十分钟做一次这太麻烦了。
另一种方法是每次我想运行它时进行git-commit,这样我就可以在需要时回来查看原始版本,但这会使提交历史非常脏,所以我认为这比另外一个。
我也尝试了python -m pdb -m script.py
,但它显示了相同的“行n的修改版本”,就像普通的追溯一样。
那么,我可以每十分钟练习一次实用的解决方案吗?
而不是每次运行脚本时都提交,只需使用git stashing,这样就不会将dirty commits
添加到历史记录中。
所以在运行脚本之前,git stash
你的本地更改,检查错误,然后git stash pop
。
阅读更多关于git stash here的信息。
此解决方案假定运行的脚本位于当前分支的HEAD
,
如果上述条件不适用,另一个解决方案是创建一个任意分支,调用它(running-script),git stash
你尚未提交的本地更改,checkout
到这个新分支,git apply stash
并运行脚本。然后结帐回原始分支,重新申请藏匿并恢复工作。
您可以简单地编写一个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
您可以将running-script和original-branch作为params传递给bash文件。
@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制作类似的方法。
我可能会给出一个过于简单的答案,并且可能不适用于所有情况。
使用PyCharm
我通常使用从几分钟到几小时完成的代码,我需要不断运行它以查看它的执行情况,并在运行时继续编码。如果失败,我会收到抛出错误的原始行。
我还必须在无GUI的Ubuntu服务器中运行它,所以我这样做是为了每次都收到正确的错误:
我并不是说它会完全避免,但你可以减少这个错误。如果您将所有逻辑编码在一个文件中,那么就停止这样做。
以下是一些推荐..
现在,如果有任何异常发生,那么它的回溯可能会扩散到更多的文件中,我猜不是所有文件都会一次性修改以实现您的更改。好消息是,如果你的异常是从一个你没有改变的文件开始的,那么很容易捕获那条线并修复它,否则找到确切的线是最小的努力。
如果你也在使用git并且你还没有提交,那么你也可以比较修订版以获得可能导致错误的确切代码。
希望这可以最小化您的问题