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

时间:2010-11-13 21:23:53

标签: wolfram-mathematica

当笔记本超出一些功能时我遇到的一种典型情况 - 我评估一个表达式,但是我得到 Beep 而不是正确的答案,然后是几十个无用的警告,然后是“进一步的输出” ...将被压制“

我觉得有用的一件事 - 在函数内部使用类似Python的“断言”来强制内部一致性。还有其他提示吗?

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

编辑11/14 警告雪崩的一般原因是子表达式评估为“坏”值。这会导致父表达式计算为“坏”值,并且此“不良”会一直传播到根目录。内置评估一路上注意到不良并产生警告。 “坏”可能意味着表达错误的头部,列表错误的元素数量,负定的矩阵而不是正定等等。一般来说,它不适合父表达式的语义。

解决这个问题的一种方法是重新定义所有函数,以便在“输入错误”时返回未评估的值。这将处理内置函数生成的大多数消息。执行像“Part”这样的结构操作的内置函数仍会尝试评估您的价值并可能产生警告。

将调试器设置为“中断消息”可防止出现大量错误,尽管将其一直打开似乎有点过分

5 个答案:

答案 0 :(得分:10)

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

  1. 正确输入参数并设置运行函数的条件,
  2. 正确且一致地处理生成的错误,
  3. 简化您应用这些步骤的方法。
  4. 正如Samsdram指出的那样,正确输入你的功能将有很大帮助。不要忘记:形式的Pattern,因为有时更容易表达这种形式的某些模式,例如x:{{_, _} ..}。显然,如果这还不够PatternTest s(?)和Condition s(/;)是可行的方法。 Samdram很好地介绍了这一点,但我想补充一点,你可以通过纯函数创建自己的模式测试,例如f[x_?(Head[#]===List&)]相当于f[x_List]。注意,当使用纯函数的&符号时,括号是必需的。

    处理生成错误的最简单方法显然是Off,或更多本地Quiet。在大多数情况下,我们都同意完全关闭我们不想要的消息是一个坏主意,但Quiet当你知道你正在做一些会产生抱怨的事情时会非常有用,但是否则是正确的。

    ThrowCatch有自己的位置,但我觉得它们只能在内部使用,而您的代码应该通过Message工具传达错误。可以使用与设置使用消息相同的方式创建消息。我相信可以使用函数CheckCheckAbortAbortProtect构建连贯错误策略的关键。

    实施例

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

    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解决方案发挥作用的地方,通过创建一个可以一致使用的方法。不幸的是,他的解决方案有一个致命的缺陷:你失去了语法高亮设施的支持。所以,这是我从上面挂入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/:)规范告诉Mathematica,当MakeCheckedReader的LHS上找到SetDelayed时,请将其替换为所需的函数定义。另请注意Quiet的使用情况;否则,它会抱怨方程右侧出现的模式a_b_

    编辑2 Leonid指出了在定义已检查的阅读器时如何能够使用file而不是file$。更新的解决方案如下:

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

    他的answer解释了改变的原因。如上所述定义myReader并检查其定义,我们得到

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

答案 1 :(得分:8)

我迟到了参加聚会,并接受了所有答案,但我想指出表格的定义:

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

在这种情况下非常有用。这种定义可以在最终拯救并决定该定义毕竟不适用之前执行复杂的计算。

我将说明如何在another SO question的特定案例的背景下使用它来实现各种错误处理策略。问题是搜索固定的对列表:

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}};

找到第二个组件大于或等于指定值的第一对。找到该对后,将返回其第一个组件。在Mathematica中有很多方法可以写这个,但这里有一个:

f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f0[100] (* returns 8 *)

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

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传统使原始表达式保持不变。这是模块[... /; ...]模式可以帮助:

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

请注意,如果最终结果是空列表并且原始表达式返回未评估, f2 将完全退出 - 通过添加/的简单权宜之计实现;条件到最后的表达。

如果出现“未找到”案例,可能会决定发出有意义的警告:

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

通过此更改,将返回相同的值,但会在“未找到”的情况下发出警告消息。新定义中的 Null 返回值可以是任何内容 - 不使用它。

除了有缺陷的客户端代码之外,还可以进一步确定“未找到”的情况根本不会发生。在这种情况下,应该导致计算中止:

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

总之,这些模式很容易应用,因此可以处理定义域之外的函数参数。在定义函数时,花一些时间来决定如何处理域错误是值得的。它减少了调试时间。毕竟,几乎所有函数都是Mathematica中的部分函数。考虑一下:一个函数可能被调用一个字符串,一个图像,一个歌曲或成群的纳米机器人(在Mathematica 9中,也许)。

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

之前的多重定义函数
Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...

答案 2 :(得分:3)

这里的问题基本上是一种类型。一个函数产生错误的输出(不正确的类型),然后将其输入到许多后续函数中,从而产生大量错误。虽然Mathematica没有像其他语言那样的用户定义类型,但您可以在函数参数上进行模式匹配而无需太多工作。如果匹配失败,则函数不会评估,因此不会发出错误的蜂鸣声。语法的关键部分是“/;”这是在一些代码的末尾,然后是测试。一些示例代码(以及输出在下面)。

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

Output:
2
Average[$Failed]

如果测试更简单,还有另一个符号可以进行类似的模式测试“?”并且在模式/函数声明中的参数之后立即执行。另一个例子如下。

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

Output:
square[{1, 2, 3}]
9

答案 3 :(得分:3)

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

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

答案 4 :(得分:3)

我希望获得的是一种定义一般程序来捕获错误传播的方法,而不需要根据我现在编写函数的方式进行根本改变,优先不添加实质类型。

这是一个尝试:

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;  

这个几乎满足了我的前提。

修改

也许为函数添加“裸”定义也很方便,不会进行错误检查。我们可能会将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[]]