【问题标题】:Preventing avalanche of runtime errors in Mathematica防止 Mathematica 中的运行时错误雪崩
【发布时间】:2010-11-13 21:23:53
【问题描述】:

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

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

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

编辑 11/14 警告雪崩的一般原因是子表达式的计算结果为“坏”值。这会导致父表达式评估为“坏”值,并且这种“坏”一直传播到根。在此过程中评估的内置插件会注意到不良情况并产生警告。 “坏”可能意味着一个表达式的头部错误、元素数量错误的列表、负定矩阵而不是正定矩阵等。通常它不符合父表达式的语义。

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

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

【问题讨论】:

  • 我一直以为只有我一个人:)
  • 只需禁用哔声
  • 我最喜欢这个,不是因为我期待一些关于这个主题的启发,而是为了产生共鸣
  • @belisarius 你应该更加乐观。我总是这么告诉你。
  • 在 Mma8 中现在有一个内置的assert 函数。如果您将$AssertFunction 设置为Abort[],那么它的行为将与您问题中的行为基本相同。

标签: wolfram-mathematica


【解决方案1】:

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

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

正如Samsdram 所指出的,正确键入您的函数会有很大帮助。不要忘记Pattern: 形式,因为有时用这种形式表达一些模式更容易,例如x:{{_, _} ..}。显然,当这还不够时,PatternTests (?) 和 Conditions (/;) 是可行的方法。 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,当在 SetDelayed 的 LHS 上找到 MakeCheckedReader 时,将其替换为所需的函数定义。另外,请注意Quiet 的使用;否则,它会抱怨出现在等式右侧的模式a_b_

编辑 2Leonid 指出了在定义检查阅读器时如何使用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}

【讨论】:

  • 感谢您指出该缺陷,尽管我不太确定它有多“致命”,因为您可以“正常”检查函数语法,并以最小的努力将其转换为 /circleminus 语法。我还在敲这个。我真的很想找到一种非侵入式的方式来中止错误,而无需额外的代码。
  • 顺便说一句 +1 ...我需要调查 TagSet ...从未使用过它
  • 再一次,像这样的答案让我每天都回来:-)。虽然,我不得不说手动绕过 Mathematica 的弱类型似乎有点违反直觉。我遇到了这样一个问题,即在大型项目(比如说 4 或 5 层模块和功能)中这样做会显着减慢速度,并且从实现必要的错误消息传播回主例程会增加许多复杂性。
  • @belisarius: TagSetUpSet (^=) 和 UpSetDelayed (^:=) 为相关函数生成上值。由于内置函数是Protected,因此很难创建具有任何数学行为的新对象。这些函数通过将转换与对象本身而不是操作相关联来解决这个问题。我从 Mathematica 的 Quantum Methods (amazon.com/Quantum-Methods-Mathematica-James-Feagin/dp/…) 中了解到它们
【解决方案2】:

我迟到了,我得到了一个公认的答案,但我想指出表单的定义:

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 有一个强大的传统,即每当使用其域之外的参数对函数求值时,保持原始表达式不变。这是 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] *)

请注意,如果最终结果是空列表并且原始表达式未计算返回,则 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_] := ... /; ...

【讨论】:

  • 谢谢,这似乎是一个有用的模式。我不明白“Condition[...,Message]”的含义...文档表明它仅在第二个参数的计算结果为“True”时才匹配,但 Message 返回 Null,那么它为什么会起作用?
  • 另一个问题,有没有一种简单的方法可以中途中止评估并返回未评估的结果?现在,我执行 If[somethingbadappened,Abort[]],但返回 $Aborted
  • @Yaroslav Bulatov:当条件是定义的保护时,除 True 以外的任何值都将导致评估器继续下一个匹配的定义(如果有)。在这种情况下,Null =!= True 所以评估器继续前进——但没有进一步的匹配定义,所以表达式保持不变。
  • @Yaroslav Bulatov:严格来说,中止和返回未评估是相互排斥的,但我想我知道你的意思:f[x_] := Module[{t}, t = someComplexComputation[x] ;更复杂的计算[t] /; !somethingBadHappened[t] ]
  • +1,用于处理一致生成的错误的一个很好的示例/模式。
【解决方案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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      我希望得到一种方法来定义一个通用过程来捕获错误传播,而无需从根本上改变我现在编写函数的方式,最好不添加大量输入。

      这是一个尝试:

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

      【讨论】:

      • 根据我上面关于此解决方案无法利用语法高亮系统的评论,请查看函数 SyntaxInformation。根据帮助文件,它允许您告诉 Mathematica 如何突出显示事物。我还没有尝试过,但是您也许可以在:c-: 的两侧正确突出显示args 中的符号。
      • +1 用于为自定义定义运算符使用中缀符号。通过为:c-: 定义一个 DownValue,您将不必使用 ` /。 funcDef`.
      猜你喜欢
      • 1970-01-01
      • 2014-07-23
      • 2022-07-07
      • 2014-07-21
      • 2014-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多