防止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它总是将其打开
正如其他人所指出的那样,有三种方法可以一致地处理错误:
- 正确地input参数并设置你的函数运行的条件,
- 处理正确和一致的错误生成,和
- 简化您的方法来应用这些步骤。
正如Samsdram指出的那样,正确地input你的函数将会有很大的帮助。 不要忘记Pattern
forms,因为有时用这种forms表示一些模式比较容易,例如x:{{_, _} ..}
。 很显然,当没有足够的PatternTest
s( ?
)和Condition
s( /;
)是要走的路。 Samdram覆盖的很好,但我想补充一点,你可以通过纯函数创build自己的模式testing,例如f[x_?(Head[#]===List&)]
等价于f[x_List]
。 请注意,使用纯函数的&号forms时,括号是必需的。
处理产生的错误最简单的方法显然是Off
,或更本地Quiet
。 大多数情况下,我们都可以同意,彻底closures我们不想要的信息是一个坏主意,但是当你知道你正在做一些会引起投诉的事情时, Quiet
可能是非常有用的,但在其他方面是正确的。
Throw
和Catch
有他们的地方,但我觉得他们应该只在内部使用,你的代码应通过Message
设施沟通错误。 消息可以按照与设置使用消息相同的方式创build。 我相信一个连贯的错误策略的关键可以使用functionCheck
, CheckAbort
, AbortProtect
。
例
我的代码中的一个例子是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[]]