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

当笔记本超出了一些功能时,我遇到了一个典型情况 - 我评估了一个表达式,但是我没有得到正确的答案,而是听到了几十个无用的警告,接着是“进一步输出...将被抑制”

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

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

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

解决这个问题的一种方法是重新定义所有函数,使其返回未评估的“错误输入”。 这将处理由内置插件产生的大部分消息。 像“零件”那样进行结构化操作的内置插件仍将尝试评估您的价值并可能产生警告。

将调试器设置为“中断消息”可防止发生大量错误,尽管它始终保持开启状态似乎有点矫枉过正


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

  • 正确输入参数并设置您的功能运行的条件,
  • 正确和一致地处理所产生的错误,并且
  • 简化您的方法以应用这些步骤。
  • 正如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/: TagSet规范告诉Mathematica,如果在MakeCheckedReader的LHS上找到SetDelayed则将其替换为所需的函数定义。 另外,请注意使用Quiet ; 否则,它会抱怨等式右边出现的模式a_b_

    编辑2 :列昂尼德指出如何在定义选中的阅读器时能够使用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"}]
    

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

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

    我迟到了,并且接受了所有的答案,但我想指出这个形式的定义:

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

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

    我将说明如何用这个方法在另一个SO问题的特定情况下实现各种错误处理策略。 问题是搜索固定的列表对:

    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会完全保留 - 通过简单的方法添加a /; 条件到最终表达式。

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

    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_] := ... /; ...
    

    这里的问题基本上是一种类型。 一个函数产生一个错误的输出(不正确的类型),然后输入许多后续的函数,产生大量的错误。 虽然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
    
    链接地址: http://www.djcxy.com/p/35505.html

    上一篇: Preventing avalanche of runtime errors in Mathematica

    下一篇: Is Mathematica an untyped language?