Debugging 如何查找笔记本中发生错误的行?

Debugging 如何查找笔记本中发生错误的行?,debugging,wolfram-mathematica,Debugging,Wolfram Mathematica,我有一个名为myUsefulFunctions.m的Mathematica文件,其中包含一个名为mySuperUsefulFunction的函数。假设我在笔记本中调用mySuperUsefulFunction并得到以下错误: Part::pspec: Part specification #1 is neither an integer nor a list of integers. >> 有没有办法找到myUsefulFunctions.m中发生此错误的那一行?我不知道有什么办法

我有一个名为myUsefulFunctions.m的Mathematica文件,其中包含一个名为mySuperUsefulFunction的函数。假设我在笔记本中调用mySuperUsefulFunction并得到以下错误:

Part::pspec: Part specification #1 is neither an integer nor a list of integers. >>

有没有办法找到myUsefulFunctions.m中发生此错误的那一行?

我不知道有什么办法可以找到文件中的那一行,我认为该行读取时没有错误

但是,您可以使用
Trace
和相关函数查看错误发生在评估链中的何处

例如:

myFunc1[x_, y_] := x[[y]]
myFunc2[a_List, n_] := Pick[a, a, myFunc1[a, n]]

myFunc2[Range@10, #1]

During evaluation of In[4]:= Part::pspec: Part specification #1 is neither an integer nor a list of integers. >>
使用
跟踪

myFunc2[Range@10, #1] // Trace // Column

{Range[10], {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}
myFunc2[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1]
Pick[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, myFunc1[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1]]
{myFunc1[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1], {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}[[#1]], {Message[Part::pspec, #1], {MakeBoxes[Part::pspec: Part specification #1 is neither an integer nor a list of integers. >>, StandardForm], RowBox[{RowBox[{Part, ::, "pspec"}], : , "\!\(\*StyleBox[\"\\\"Part specification \\\"\",  \"MT\"]\)\!\(\*StyleBox[\!\(#1\),  \"MT\"]\)\!\(\*StyleBox[\"\\\" is neither an integer nor a list of integers.\\\"\",  \"MT\"]\) \!\(\*ButtonBox[\">>\",  ButtonStyle->\"Link\",  ButtonFrame->None,  ButtonData:>\"paclet:ref/message/General/pspec\",  ButtonNote -> \"Part::pspec\"]\)"}]}, Null}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}[[#1]]}
您可以看到,就在调用
消息[Part::pspec,#1]
之前,我们有:

{myFunc1[{1,2,3,4,5,6,7,8,9,10},#1],{1,2,3,4,5,6,7,8,9,10}[[#1]

这表明调用了
myFunc1[{1,2,3,4,5,6,7,8,9,10},#1]
,这会导致对
{1,2,3,4,5,6,7,8,9,10}[[#1]]
的求值,这显然是错误的

有关更方便地使用
跟踪
,请参见此问题及其答案:

myFunc2[Range@10, #1] // Trace // Column

{Range[10], {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}
myFunc2[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1]
Pick[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, myFunc1[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1]]
{myFunc1[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, #1], {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}[[#1]], {Message[Part::pspec, #1], {MakeBoxes[Part::pspec: Part specification #1 is neither an integer nor a list of integers. >>, StandardForm], RowBox[{RowBox[{Part, ::, "pspec"}], : , "\!\(\*StyleBox[\"\\\"Part specification \\\"\",  \"MT\"]\)\!\(\*StyleBox[\!\(#1\),  \"MT\"]\)\!\(\*StyleBox[\"\\\" is neither an integer nor a list of integers.\\\"\",  \"MT\"]\) \!\(\*ButtonBox[\">>\",  ButtonStyle->\"Link\",  ButtonFrame->None,  ButtonData:>\"paclet:ref/message/General/pspec\",  ButtonNote -> \"Part::pspec\"]\)"}]}, Null}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}[[#1]]}

您可以在那里使用WolframWorkbench和调试器:


然后,您可以设置断点并逐步完成代码。

除了工作台中的调试器外,Mathematica中还内置了调试器。您可以在“评估”菜单中找到它。它没有很好的文档记录,并且很难/非常规定地使其工作。以下是如何使用它的分步说明:

假设您在“评估”菜单中打开了调试器,则窗口栏将指示这是一个调试会话,并且您将有一些调试器选项板

现在,选择一些你想作为断点的行,点击“选择时中断”文本。断点将用红色轮廓标记

然后按Shift return运行代码,并准备好稍感失望:它不起作用。似乎您无法在行级别定义断点。它必须在函数级别。此外,MMA对您可以使用的函数相当挑剔。
Print
函数显然不起作用,也不执行任务。但是
Integrate
在本例中是这样做的,但您必须选择它的头部和两个括号,并将其作为断点。如果您已经这样做,然后执行代码块,则会得到以下结果:

断点高亮显示为绿色,控制面板中的一些附加选项可用于控制进一步的程序流,堆栈窗口中有表达式。其余部分或多或少类似于标准调试器。请注意,您可以在Integrate中嵌套断点,如Cos。对于可能具有更高优先级的语言这是必要的


另一个选择是David Bailey的调试器。他在他的网站上提供了免费的调试器。我自己没有尝试过,但我知道David是一位非常能干的数学家,所以我相信它一定很好。

一个轻量级的调试功能 除了其他建议外,以下是一个函数,它对我有几次帮助:

ClearAll[debug];
SetAttributes[debug, HoldAll];
debug[code_] :=
 Internal`InheritedBlock[{Message},
   Module[{inMessage},
     Unprotect[Message];        
     Message[args___] /; ! MatchQ[First[Hold[args]], _$Off] :=
       Block[{inMessage = True},
         Print[{
            Shallow /@ Replace[#, HoldForm[f_[___]] :> HoldForm[f], 1],
            Style[Map[Short, Last[#], {2}], Red]
           } &@Drop[Drop[Stack[_], -7], 4]
         ];
         Message[args];
         Throw[$Failed, Message];
       ] /; ! TrueQ[inMessage];
    Protect[Message];
   ];
   Catch[StackComplete[code], Message]]
这基本上重新定义了
消息
,以临时进入执行堆栈,以易于理解的形式打印被调用函数的名称,加上最终的调用(导致错误消息)和错误消息本身。之后,我们通过异常退出执行,以避免产生混乱的错误消息链。

使用示例 下面是@Mr.Wizard答案中的一个示例的工作原理:

In[211]:= debug[myFunc2[Range@10,#1]]

During evaluation of In[211]:= 
    {{myFunc2,Pick,myFunc1,Part},{1,2,3,4,5,6,7,8,9,10}[[#1]]}

During evaluation of In[211]:= Part::pspec: Part specification #1 is neither 
 an integer nor a list of integers. >>

Out[211]= $Failed
(在笔记本中,有问题的函数调用被涂成了红色)。这样可以快速看到导致问题的函数调用链

下面是另一个示例:我们构造了一个自定义的
gatherBy
函数,该函数根据另一个“标记”列表收集列表中的元素,该列表的长度应与原始列表的长度相同:

listSplit[x_, lengths_] := 
   MapThread[Take[x, {##}] &, {Most[#], Rest[#] - 1}] &@
      Accumulate[Prepend[lengths, 1]];

gatherBy[lst_, flst_] := 
    listSplit[lst[[Ordering[flst]]], (Sort@Tally[flst])[[All, 2]]];
例如:

In[212]:= gatherBy[Range[10],{1,1,2,3,2,4,5,5,4,1}]
Out[212]= {{1,2,10},{3,5},{4},{6,9},{7,8}}
因为我故意让所有类型都签出,所以使用错误类型参数的调用将导致一连串令人讨厌的错误消息:

In[213]:= gatherBy[Range[10],Range[15]]//Short
  During evaluation of In[206]:= Part::partw: Part 11 of {1,2,3,4,5,6,7,8,9,10} does not exist. >>

  (* 4 more messages here *)
Out[213]//Short= {{1,2,3,4,5,6,7,8,9,10},<<14>>}
通过[Range[10]调用带有一些符号的
a
gatherBy,a]
是另一个将
debug
包装起来的示例

适用性
其他建议的方法更系统,可能更普遍,但这一方法易于应用,结果往往更容易理解(例如,与
跟踪的输出相比,跟踪的输出并不总是容易阅读)但是,我并没有经常使用它来保证它总是有效的。

我知道我以前说过,但你真的是常驻的Mathematica教授。@Mr.Wizard这是一个严重的高估,但无论如何还是要感谢:)但事实上,在这种特殊情况下,我犹豫了一下是否发布
调试
,因为我不完全确定它是否总是有效的RK和调试工具必须是完美的。然后我想——改进它的唯一方法是发布它并从其他人那里获得反馈,我就是这样做的。我经常将跟踪结果放在
表格中
。因为结果通常(总是?)是
{expr,value}
对的列表,表格可以帮助很多人。试试
Trace@Cases[{f[{a,b}],f[{a}],g[{a}],f[{a,b,c,d}]},f[x}:>Length@x]//表格形式
Trace@Cases[{f[{a,b}],f[{a},g[{a}],f[{a,b,c,d}]},f[x]>Length@x]//TableForm
@Reb.cab好吧,到目前为止,这只是一个列表,它只是一个列表。我一直在考虑扩展它,以便它可以选择性地提供更多信息。我可以想象像
TableForm
这样的东西会非常有用。忘了用becomin祝贺你了g世界上第三个获得Mathematica金牌的人。加油!我加入Sjoerd-我最诚挚的祝贺!当之无愧,期待已久。欢迎加入俱乐部:)@Sjoerd th