如何在Mathematica笔记本中找到出错的行?

30

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

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

有没有方法可以找到出现错误的代码行在 myUsefulFunctions.m 文件中的位置?

4个回答

21

一种轻量级调试函数

除了其他建议,这里提供了一个函数,在我遇到问题时曾经帮过我的几次:

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]]

这基本上是临时重新定义了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>>}

使用debug,我们可以很快地看出问题所在:

In[214]:= debug[gatherBy[Range[10],Range[15]]]

 During evaluation of In[214]:= 
    {{gatherBy,listSplit,Part},
    {1,2,3,4,5,6,7,8,9,10}[[Ordering[{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}]]]}

 During evaluation of In[214]:= Part::partw: Part 11 of {1,2,3,4,5,6,7,8,9,10} does not exist. >>

 Out[214]= $Failed

使用 debug 方法来调用 gatherBy[Range[10], a],当其中有一些符号 a 时,是另一个例子。

适用性

其他建议的方法更加系统化,可能更加普遍推荐,但这种方法易于应用,并且通常会导致更容易理解的结果(例如,与不易阅读的 Trace 输出相比)。然而,我并没有经常使用它以保证它总是有效的。


1
我知道我以前说过这话,但你真的是Mathematica的专家。 - Mr.Wizard
@Mr.Wizard 这是一个过高的估计,但还是谢谢 :) 但事实上,在这种特殊情况下,我犹豫是否要发布“debug”,因为我不完全确定它总是有效的,而调试工具必须是无缺陷的。然后我想 - 唯一改进它的方法就是发布它并从其他人那里获得反馈,所以我这样做了。 - Leonid Shifrin
1
我经常在TableForm中放置Trace的结果。由于结果通常(总是?)是一个{expr,value}对的列表,因此TableForm非常有用。请尝试使用Trace@Cases[{f [{a,b}],f[{a}],g[{a}],f[{a,b,c,d}]},f[x_]:> Length @ x] // TableFormTrace@Cases[{f[{a, b}], f[{a}], g[{a}], f[{a, b, c, d}]}, f[x_] -> Length@x] // TableForm进行比较。 - Reb.Cabin
1
@Reb.Cabin 嗯,到目前为止,这只是一个列出通往问题所在的标题的列表,所以它只是一个列表。我一直在考虑扩展它,使其可以选择性地提供更多信息。我可以想象像TableForm这样的东西会非常有用。 - Leonid Shifrin

20
除了Workbench中的调试器外,Mathematica还有一个内置的调试器。您可以在“评估”菜单中找到它。它没有很好的文档记录,并且相当难以使用/非传统。以下是如何使用它的逐步说明:
假设您已经在“评估”菜单中打开了调试器,您的窗口栏将指示它是一个调试会话,并且您将拥有一些调试器面板。

enter image description here

现在选择您想要作为断点的行数,然后单击“在所选处中断”文本。断点将用红色轮廓标记。

enter image description here

按下Shift-return运行代码,准备好稍微失望一下:它不起作用。似乎你不能在行级别上定义断点,必须在函数级别上进行。此外,MMA对可以使用的函数非常挑剔。显然Print函数不起作用,赋值也是如此。然而,在这个例子中,Integrate函数可以使用,但你必须选择它的头和双括号,并将其作为断点。如果你已经这样做了,然后执行代码块,你会得到这个结果:

enter image description here

断点将被突出显示为绿色,控制面板中会出现其他选项以进一步控制程序流程,并且堆栈窗口中会有表达式。其余部分与标准调试器更或多或少相似。请注意,您可以像在Integrate中的Cos函数一样嵌套断点。对于具有深层嵌套结构的语言,这是必不可少的。

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


我相信我已经找到了正确设置断点的方法,请看这里:http://mathematica.stackexchange.com/a/119916/1871 - xzczd

17

我不知道有没有办法找到在文件中的哪一行出现了错误,因为我假设这个文件是正确读取的。

不过,你可以使用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. >>

使用 Trace

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]]}
你会发现在调用 Message[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]] 的求值,这显然是错误的。请参见这个问题及其答案,以获得更方便使用 Trace 的方法: https://stackoverflow.com/q/5459735/618728

2
忘了祝贺你成为世界上第三个获得Mathematica金徽章的人。继续加油! - Sjoerd C. de Vries
我加入Sjoerd - 我最真诚的祝贺!实至名归,期待已久。欢迎加入俱乐部 :) - Leonid Shifrin
@Sjoerd非常感谢你!如果你没有其他安排,你肯定会比我更快完成它。 - Mr.Wizard
@Mr. "我的 " <> Sequence @@ #@# &@"祝贺!" - Dr. belisarius
@belisarius 你应该使用中缀表达式。 - Leonid Shifrin
1
@Leonid,我不行。你从来没有完成你的回答 :) - Dr. belisarius

8

1
谢谢回复,但我发现 Workbench 是一个相当不令人满意的产品。Eclipse 本来就有 bug,再在其上添加 Mathematica 功能也没什么帮助 :( 我现在用 vim 工作,并找到了自己调试问题的方法... - rjkaplan
工作台需要单独的许可证,是吗? - Reb.Cabin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接