防止Mathematica中的运行时错误雪崩

当笔记本超出了一些function时,我遇到了一个典型的情况 – 我评估一个expression式,但不是正确的答案,我得到了嘟嘟声,接着是几十个无用的警告,之后是“进一步输出……将被抑制”

有一件事我觉得很有用 – 在函数内部使用类似Python的“assert”来强化内部一致性。 任何其他提示?

Assert[expr_, msg_] := If[Not[expr], Print[msg]; Abort[], None] 

编辑11/14警告雪崩的一般原因是当子expression式评估为“坏”值时。 这会导致父expression式评估为“坏”值,而这个“坏”将一直传播到根。 一路上评估的内置插件注意到不良情况并产生警告。 “坏”可能意味着头部错误的表情,错误的元素数量表,负定matrix而不是正定matrix等等。一般来说,这是不符合母expression语义的东西。

解决这个问题的一个方法就是重新定义所有的函数,以“不好的input”返回未评估的值。 这将处理由内置插件产生的大部分消息。 像“零件”这样的结构化操作的内build程序仍会尝试评估您的价值,并可能产生警告。

将debugging器设置为“中断消息”可以防止一系列错误,尽pipe它总是将其打开

正如其他人所指出的那样,有三种方法可以一致地处理错误:

  1. 正确地input参数并设置你的函数运行的条件,
  2. 处理正确和一致的错误生成,和
  3. 简化您的方法来应用这些步骤。

正如Samsdram指出的那样,正确地input你的函数将会有很大的帮助。 不要忘记Patternforms,因为有时用这种forms表示一些模式比较容易,例如x:{{_, _} ..} 。 很显然,当没有足够的PatternTest s( ? )和Condition s( /; )是要走的路。 Samdram覆盖的很好,但我想补充一点,你可以通过纯函数创build自己的模式testing,例如f[x_?(Head[#]===List&)]等价于f[x_List] 。 请注意,使用纯函数的&号forms时,括号是必需的。

处理产生的错误最简单的方法显然是Off ,或更本地Quiet 。 大多数情况下,我们都可以同意,彻底closures我们不想要的信息是一个坏主意,但是当你知道你正在做一些会引起投诉的事情时, Quiet可能是非常有用的,但在其他方面是正确的。

ThrowCatch有他们的地方,但我觉得他们应该只在内部使用,你的代码应通过Message设施沟通错误。 消息可以按照与设置使用消息相同的方式创build。 我相信一个连贯的错误策略的关键可以使用functionCheckCheckAbortAbortProtect

我的代码中的一个例子是OpenAndRead ,它可以防止在中止读操作时保持开放stream,如下所示:

 OpenAndRead[file_String, fcn_]:= Module[{strm, res}, strm = OpenRead[file]; res = CheckAbort[ fcn[strm], $Aborted ]; Close[strm]; If[res === $Aborted, Abort[], res] (* Edited to allow Abort to propagate *) ] 

直到最近才有这个用法

 fcn[ file_String, <otherparams> ] := OpenAndRead[file, fcn[#, <otherparams>]&] fcn[ file_InputStream, <otherparams> ] := <fcn body> 

不过,这每次都很烦人。

这是belisarius解决scheme发挥作用,通过创build一个可以一致使用的方法。 不幸的是,他的解决scheme有一个致命的缺陷:你失去了语法高亮设施的支持。 所以,这里有一个替代scheme, OpenAndRead从上面OpenAndRead

 MakeCheckedReader /: SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] := Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &]; fcn[file_Symbol, symbols] := a), {RuleDelayed::"rhs"}] 

有用法

 MakeCheckedReader[ myReader, a_, b_ ] := {file$, a, b} (*as an example*) 

现在,检查myReader的定义给出了两个定义,就像我们想要的那样。 在函数体中, file必须被称为file$ 。 (我还没有想出如何命名文件var如我所愿。)

编辑MakeCheckedReader作品本身并没有做任何事情。 相反, TagSet/: TagSet规范告诉Mathematica,如果在MakeCheckedReader的LHS上findSetDelayed则将其replace为所需的函数定义。 另外,请注意使用Quiet ; 否则,会抱怨等式右边出现的模式a_b_

编辑2 : 列昂尼德指出如何定义一个检查阅读器时能够使用file不是file$ 。 更新后的解决scheme如下:

 MakeCheckedReader /: SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] := Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &]; SetDelayed @@ Hold[fcn[file_Symbol, symbols], a]), {RuleDelayed::"rhs"}] 

他的这个答案解释了变化的原因。 如上所述定义myReader ,并检查它的定义,我们可以得到

 myReader[file$_String,a_,b_]:=OpenAndRead[file$,myReader[#1,a_,b_]&] myReader[file_Symbol,a_,b_]:={file,a,b} 

我迟到了,还有一个被接受的答案,但是我想指出这个forms的定义:

 f[...] := Module[... /; ...] 

在这方面非常有用。 这种定义在进行最后的救助之前可以进行复杂的计算,并决定该定义毕竟不适用。

我将从另一个SO问题来说明如何用这个方法来实现各种error handling策略。 问题是search一个固定的列表对:

 data = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 15}, {5, 29}, {6, 50}, {7, 88}, {8, 130}, {9, 157}, {10, 180}, {11, 191}, {12, 196}, {13, 199}, {14, 200}}; 

find第二个分量大于或等于指定值的第一个分组。 一旦find这一对,它的第一个组件将被返回。 有很多方法可以在Mathematica中编写,但是这里有一个:

 f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1] f0[100] (* returns 8 *) 

现在的问题是,如果使用无法find的值调用函数,会发生什么情况?

 f0[1000] error: First::first: {} has a length of zero and no first element. 

这个错误信息是神秘的,至多没有提供什么问题的线索。 如果这个函数在调用链中被深度调用,那么可能会发生类似不透明错误的级联。

处理这种特殊情况有多种策略。 一个是改变返回值,以便成功案例可以从一个失败案例中区分出来:

 f1[x_] := Cases[data, {t_, p_} /; p >= x :> t, {1}, 1] f1[100] (* returns {8} *) f1[1000] (* returns {} *) 

然而,Mathematica有一个强大的传统,当一个函数被其域外的参数评估时,原始expression式不会被修改。 这是Module [… / …]模式可以帮助:

 f2[x_] := Module[{m}, m = Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]; First[m] /; m =!= {} ] f2[100] (* returns 8 *) f2[1000] (* returns f2[1000] *) 

请注意,如果最终结果是空列表,并且原始expression式返回未评估, f2将完全保留 – 通过简单的方法添加a /; 条件到最后的expression。

如果发现“未find”情况,可能会决定发出有意义的警告:

 f2[x_] := Null /; Message[f2::err, x] f2::err = "Could not find a value for ``."; 

有了这个改变,相同的值将被返回,但在“未find”的情况下会发出警告信息。 新定义中的返回值可以是任何东西 – 它不被使用。

有人可能会进一步决定,“找不到”的情况根本不可能发生,除了在客户代码错误的情况下。 在这种情况下,应该导致计算中止:

 f2[x_] := (Message[f2::err, x]; Abort[]) 

总之,这些模式很容易应用,以便处理定义域之外的函数参数。 在定义函数时,花费一些时间来决定如何处理域错误。 它减less了debugging时间。 毕竟,几乎所有的函数都是Mathematica中的部分函数。 考虑一下:一个函数可能会被调用一个string,一个图像,一首歌或者一群纳米机器人(也许在Mathematica 9中)。

最后一个注意事项…我应该指出,当使用多个定义来定义和重新定义函数时,由于“遗留”定义,很容易得到意想不到的结果。 作为一般原则,我强烈推荐使用Clear的多重定义函数:

 Clear[f] f[x_] := ... f[x_] := Module[... /; ...] f[x_] := ... /; ... 

这里的问题实质上是一种types。 一个函数产生一个错误的输出(不正确的types),然后input许多后续的函数,产生大量的错误。 虽然Mathematica不像其他语言那样具有用户定义的types,但可以在函数参数上进行模式匹配,而不需要太多工作。 如果匹配失败,函数不计算,因此不会发出错误提示音。 关键的语法是“/” 它在一些代码的最后,然后是testing。 一些示例代码(和输出如下)。

 Input: Average[x_] := Mean[x] /; VectorQ[x, NumericQ] Average[{1, 2, 3}] Average[$Failed] Output: 2 Average[$Failed] 

如果testing更简单,还有另一个符号做类似的模式testing“?” 并在模式/函数声明中的参数之后进行。 另一个例子如下。

 Input: square[x_?NumericQ] := x*x square[{1, 2, 3}] square[3] Output: square[{1, 2, 3}] 9 

它可以帮助定义一个catchall定义来select错误条件并以有意义的方式报告:

 f[x_?NumericQ] := x^2; f[args___] := Throw[{"Bad Arguments: ", Hold[f[args]]}] 

所以你的顶级通话可以使用Catch [],或者你可以让它冒泡:

 In[5]:= f[$Failed] During evaluation of In[5]:= Throw::nocatch: Uncaught Throw[{Bad Args: ,Hold[f[$Failed]]}] returned to top level. >> Out[5]= Hold[Throw[{"Bad Args: ", Hold[f[$Failed]]}]] 

我喜欢得到的是一种定义一个通用的过程来捕捉错误传播的方法,而不需要现在就改变我写函数的方式,而不需要增加实质性的input。

这里是一个尝试:

 funcDef = t_[args___] :c-: a_ :> ReleaseHold[Hold[t[args] := Check[a, Print@Hold[a]; Abort[]]]]; Clear@v; v[x_, y_] :c-: Sin[x/y] /. funcDef; ?v v[2, 3] v[2, 0] 

:c-:当然是Esc c-Esc,一个未使用的符号(\ [CircleMinus]),但任何人都可以。

输出:

 Global`v v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]] Out[683]= Sin[2/3] During evaluation of In[679]:= Power::infy: Infinite expression 1/0 encountered. >> During evaluation of In[679]:= Hold[Sin[2/0]] Out[684]= $Aborted 

我们改变的是

  v[x_, y_] := Sin[x/y] 

通过

  v[x_, y_] :c-: Sin[x/y] /. funcDef; 

几乎满足我的前提。

编辑

也许为函数添加一个“nude”定义也是很方便的,它不会进行错误检查。 我们可以将funcDef规则更改为:

 funcDef = t_[args___] \[CircleMinus] a_ :> {t["nude", args] := a, ReleaseHold[Hold[t[args] := Check[a, Print@Hold[a]; Abort[]]]] }; 

去找

  v[x_, y_] :c-: Sin[x/y] /. funcDef; 

这个输出

 v[nude,x_,y_]:=Sin[x/y] v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]