在大型Mathematica项目中设置诊断错误信息

18
每当我创建一个大的Mathematica项目时,我都会遇到这个问题:Preventing avalanche of runtime errors in Mathematica,即Mathematica的错误消息是不透明的、过时的、且数量众多。
因此,我们需要禁用Mathematica自己的所有错误消息,并在每个函数和模块中实现自己的类型检查和错误消息。然而,我没有找到一个简单有效的方法来实现此目的,并最终出现一些函数在20个函数调用深度处生成错误,然后获得整个级联的错误消息,一直返回到主例程。
您将如何设置一个简单的机制,它仅在遇到错误的函数处生成一条错误消息和一个简单的函数调用链列表?
编辑:由于它已经出现在几个答案中; 我特别寻求轻量级的输出(否则我可以坚持使用Mathematica的错误消息),并且计算开销也要轻量级。因此,虽然Stack和Trace对开销较小,但在复杂的项目中,它们的输出不容易快速解析,需要进行一些简化的工作。

我喜欢简单机制的想法!Mathematica的“警告”通常对应于无法恢复的错误,因此继续评估没有任何意义。有一个能够在第一条消息出现时中断评估的功能会很好。 - Yaroslav Bulatov
通过对 Message[] 进行操作,终止第一个消息后的求值... 这是一种有趣的方法! - Timo
2
顺便说一句,第7版中的内置调试器执行类似的操作--如果你选中“在消息处断点”,它将在每个消息处中断并显示堆栈。 - Yaroslav Bulatov
我重写了我的答案。它仍需要一些工作,但我认为这是在不重写现有代码的情况下进行类型检查和打印堆栈的最简单方法。 - Samsdram
@Samsdram 不错的想法,但是你应该使用Abort[]而不是Return[False]。否则,如果错误在多个函数调用深度中发生,你最终会得到一个巨大的错误级联。 - Timo
我认为通过返回false,测试的模式根本不会被评估。我认为这样做很好,这样您就可以看到生成错误的整个调用。但是,如果所有函数都没有这种类型检查,则可能会导致级联错误,你说得对。 - Samsdram
5个回答

9

YAsI - 又一个(愚蠢的?)想法...

重新阅读您的问题...

那么,这个想法就是禁用Mathematica自己的所有错误消息,并在每个函数和模块中实现自己的类型检查和错误消息。

找到了这个:

$MessagePrePrint = ( #; Print[Stack[_][[;; -5]]]; Abort[]) &  

v[x_, y_] := w[x, y];
w[x_, y_] := x/y;

StackComplete@v[1, 0];

During evaluation of In[267]:= {StackComplete[v[1,0]];,
          StackComplete[v[1,0]], v[1,0], w[1,0], 1/0, 1/0, Message[Power::infy,1/0]}

Out[267]= $Aborted

结论:...在第一条消息处中止并留下“合理”的堆栈跟踪。 “合理”意味着“应该改进”。

但它完全不会干扰!


1
非常好!我需要尝试一下这个,但是看起来在$MessagePrePrint上使用适当的格式意味着你只需要在出错/类型检查的地方将StackComplete@funcName[args]添加到函数中的一个单独行。 - Timo
@Timo,请在有进展时发布状态更新。我也会开始测试这个版本。我认为追踪过程太线性了(采用TreePlot之类的工具可能会更好),参数记录是对我以前的尝试(你的theStack思路)的一个很棒的免费加成。此外,设置一个debug标志(If[deBug,..])可能允许相同的代码运行几乎没有性能损失。 - Dr. belisarius
其他的解决方案也很好,但您因样式而获得了勾选标志!我稍后会尝试用简明的示例编辑原始帖子。 - Timo

3

关于提取堆栈的建议,也许可以依赖Trace?

以下是Chris Chiasson的一个使用Trace的示例。此代码将1 + Sin[x + y] + Tan[x + y]的评估树保存到~/temp/msgStream.m中。

Developer`ClearCache[];
SetAttributes[recordSteps, HoldAll];
recordSteps[expr_] :=

  Block[{$Output = List@OpenWrite["~/temp/msgStream.m"]}, 
   TracePrint[Unevaluated[expr], _?(FreeQ[#, Off] &), 
    TraceInternal -> True];
   Close /@ $Output;
   Thread[
    Union@Cases[
      ReadList["~/temp/msgStream.m", HoldComplete[Expression]], 
      symb_Symbol /; 
        AtomQ@Unevaluated@symb && 
         Context@Unevaluated@symb === "System`" :> 
       HoldComplete@symb, {0, Infinity}, Heads -> True], 
    HoldComplete]
   ];
recordSteps[1 + Tan[x + y] + Sin[x + y]]

回答Samsdram的问题,下面的代码(也来自Chris)给出了Mathematica表达式的求值树。这里是MathGroup的帖子,其中包含源代码和示例。

(Attributes@# = {HoldAllComplete}) & /@ {traceToTreeAux, toVertex, 
  HoldFormComplete, getAtoms, getAtomsAux}
MakeBoxes[HoldFormComplete[args___], form_] := 
 MakeBoxes[HoldForm[args], form]
edge[{head1_, pos1_, xpr1_}, {head2_, pos2_, xpr2_}] := 
 Quiet[Rule[{head1, vertexNumberFunction@pos1, xpr1}, {head2, 
    vertexNumberFunction@pos2, xpr2}], {Rule::"rhs"}]
getAtomsAux[atom_ /; AtomQ@Unevaluated@atom] := 
 Sow[HoldFormComplete@atom, getAtomsAux]
getAtomsAux[xpr_] := Map[getAtomsAux, Unevaluated@xpr, Heads -> True]
getAtoms[xpr_] := Flatten@Reap[getAtomsAux@xpr][[2]]
toVertex[traceToTreeAux[HoldForm[heldXpr_], pos_]] := toVertex[heldXpr]
toVertex[traceToTreeAux[HoldForm[heldXprs___], pos_]] := 
 toVertex@traceToTreeAux[Sequence[], pos]
(*this code is strong enough to not need the ToString commands,but \
some of the resulting graph vertices give trouble to the graphing \
routines*)
toVertex[
  traceToTreeAux[xpr_, pos_]] := {ToString[
   Short@Extract[Unevaluated@xpr, 0, HoldFormComplete], StandardForm],
   pos, ToString[Short@First@originalTraceExtract@{pos}, StandardForm]}
traceToTreeAux[xpr_ /; AtomQ@Unevaluated@xpr, ___] := Sequence[]
traceToTreeAux[_HoldForm, ___] := Sequence[]
traceToTreeAux[xpr_, pos_] := 
 With[{lhs = toVertex@traceToTreeAux[xpr, pos], 
   args = HoldComplete @@ Unevaluated@xpr}, 
  Identity[Sequence][
   ReleaseHold[
    Function[Null, edge[lhs, toVertex@#], HoldAllComplete] /@ args], 
   ReleaseHold@args]]
traceToTree[xpr_] := 
 Block[{vertexNumber = -1, vertexNumberFunction, 
   originalTraceExtract}, 
  vertexNumberFunction[arg_] := 
   vertexNumberFunction[arg] = ++vertexNumber; 
  originalTraceExtract[pos_] := 
   Extract[Unevaluated@xpr, pos, HoldFormComplete]; {MapIndexed[
    traceToTreeAux, Unevaluated@xpr, {0, Infinity}]}]
TraceTreeFormPlot[trace_, opts___] := 
  Block[{$traceExpressionToTree = True}, 
   Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
   SparseArray`ExpressionToTree[trace, Infinity] = traceToTree@trace; 
   With[{result = ToExpression@ToBoxes@TreeForm[trace, opts]}, 
    Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
    SparseArray`ExpressionToTree[trace, Infinity] =.; 
    Through@{Update, Protect, Update}@SparseArray`ExpressionToTree; 
    result]];

TraceTreeFormPlot[Trace[Tan[x] + Sin[x] - 2*3 - 55]]

请问您能否发布链接? - Dr. belisarius
抱歉,我没有链接,这是我多年前从某个MathGroup邮件列表消息中复制的。 - Yaroslav Bulatov
Trace非常好用,但是你会发现需要花费很多时间来使其输出可读 :-). 而且它感觉就像是在刀枪战中使用轨道导弹指挥一样。我喜欢将Trace的输出保存在其他地方,直到可能需要它。 - Timo

3
试图实现@Timo的想法(theStack)

这个想法还不完整,可能存在缺陷,但是我们需要继续思考:

Clear["Global`*"];
funcDef = t_[args___]  \[CircleMinus] a_ :>
   {t["nude", args] := a,
    ReleaseHold[Hold[t[args] :=
       (If[! ValueQ[theStack], theStack = {}];
        AppendTo[theStack, ToString[t]];
        Check[ss = a, Print[{"-TheStack->", Evaluate@theStack}]; 
         Print@Hold[a]; Abort[]];
        theStack = Most@theStack;
        Return[ss])
      ]]};
v[x_, y_]\[CircleMinus]  (Sin@ g[x, y]) /. funcDef;
g[x_, y_]\[CircleMinus]  x/y /. funcDef;
v[2, 3]
v[2, 0]

输出:

Out[299]= Sin[2/3]

During evaluation of In[295]:= Power::infy: Infinite expression 1/0 encountered. >>

During evaluation of In[295]:= {-TheStack->,{v,g}}

During evaluation of In[295]:= Hold[2/0]

Out[300]= $Aborted

顺便说一句……我没有找到让funcDef将函数参数存储在theStack中的方法。有什么想法吗? - Dr. belisarius
我在考虑只是推送和删除列表,例如,AppendTo [theStack,{"funcName",{funArg1,..}}]。 - Timo
@Timo,这个意图是处理消息,通过中止并转储“theStack”来实现。我仍然无法输入函数参数,这显然是一个缺陷。 - Dr. belisarius
@belisarus 在玩弄你的解决方案一段时间后,我明白了你的意思。非常有趣的方法!我从来没有想过把整个东西变成一个 lambda 函数。 - Timo
@belisarus,请看一下我在Timo的回答中的评论。 - rcollyer

3
为了开始这个话题,这里有一个我一直在考虑的想法:创建一个伪堆栈。
首先,创建一个全局变量 theStack={},然后在每个函数模块中以 AppendTo[theStack,"thisFuncName"] 开始并以 theStack=Most@theStack 结束。假设函数调用的深度较浅(约为几十个),这样不会增加任何显著的开销。
然后实现你自己的输入/错误检查,并在出现错误时使用 Print@theStack;Abort[];
这种方法的改进可以包括:
  1. 找到一种动态获取“thisFunctionName”的方法,以便将AppendTo[]转换为所有FunctionModule的相同函数调用。
  2. 使用Message[]而不是Print[]
  3. 将其他重要变量/状态信息推送到theStack

尝试在我的答案中实现(第一次尝试) - Dr. belisarius
“堆栈”与“跟踪”有相同的问题,需要做很多额外的工作才能获得简单易读的输出。在编写/调试大型项目时,我几乎总是只需要知道哪个函数生成了错误,从哪里调用以及使用了什么参数即可。TraceStack都提供了这些信息,但淹没在其他大量信息中。我希望能够一眼看出去哪里找到错误。 - Timo

2

也许我们一直想得太复杂了。如果我们只是稍微调整一下参数的模式匹配呢?例如,如果我们修改函数以检查数字数量,并添加一些代码来在失败时打印错误信息。例如:

 TypeNumeric[x_] :=   If[! NumericQ[Evaluate[x]],
 Print["error at "]; Print[Stack[]];    Print["Expression "]; Print[x];    Print["Did   
 not return a numeric value"];Return[False], 
 (*Else*)
 Return[True];] 
 SetAttributes[TypeNumeric, HoldAll];

步骤2:如果您有一个需要数字量的函数f[x_],只需使用标准模式测试编写即可。

Input:
f[x_?TypeNumeric] := Sqrt[x]
f[Log[y]]
f[Log[5]]
Output:
error at 
{f}
Expression 
Log[y]
Did not return a numeric value
f[Log[y]]

Sqrt[Log[5]]

我相信这会起作用,而且使得强大的类型检查就像写一个或两个函数一样简单。问题是,这可能非常低效,因为该代码评估表达式x两次,一次用于类型检查,一次用于实际使用。如果涉及昂贵的函数调用,则可能很糟糕。
我还没有找到解决这个问题的方法,并欢迎在这方面提出建议。连续性是解决这个问题的方法吗?
希望这可以帮助。

@Samsdram 你是在寻找类似于TreeForm[]的东西吗? - Dr. belisarius
为什么合同比If、Print、Abort检查更好?严格的类型检查对于大型/协作项目可能很有用,但就个人而言,我希望有一些能够在不需要改变我现在编写Mathematica函数的方式的情况下帮助处理错误的东西。 - Yaroslav Bulatov
顺便提一下,我在我的答案中添加了一个编辑,它会给出类似于TreeForm的东西,但是针对的是求值树而不是语法解析树。 - Yaroslav Bulatov
同意@Yaroslav的观点。对于大型项目来说,这似乎是正确的选择,但对于基本计算来说过于繁琐。 - Dr. belisarius
合同比If/Print/Abort等更好,因为错误通常会距离两个或三个函数调用。例如,定义f[x_]:=x/;x>=0,然后调用f[g[x]]。简单的工具可以在它到达函数f时捕获错误,但当你有很多代码时,你真正想知道的是g[x]是问题的原因。我不确定你是否可以使用之前建议的堆栈方法来做到这一点,因为在错误被f[x]捕获之前,g[x]将已经完成评估,堆栈上应该没有记录。对于小项目来说有些过度,但问题是关于大项目的。 - Samsdram
显示剩余3条评论

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