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

发布于 2024-10-02 06:53:25 字数 564 浏览 2 评论 0原文

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

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

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

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

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

将调试器设置为“消息中断”可以防止大量错误,尽管始终打开它似乎有些过分

A typical situation I run into when notebook grows beyond a couple of functions -- I evaluate an expression, but instead of correct answer I get Beep followed by dozens of useless warnings followed by "further Output of ... will be suppressed"

One thing I found useful -- use Python-like "assert" inside functions to enforce internal consistency. Any other tips?

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

edit 11/14
A general cause of a warning avalanche is when a subexpression evaluates to "bad" value. This causes the parent expression to evaluate to a "bad" value and this "badness" propagates all the way to the root. Built-ins evaluated along the way notice the badness and produce warnings. "Bad" could mean an expression with wrong Head, list with wrong number of elements, negative definite matrix instead of positive definite, etc. Generally it's something that doesn't fit in with the semantics of the parent expression.

One way do deal with this is to redefine all your functions to return unevaluated on "bad input." This will take care of most messages produced by built-ins. Built-ins that do structural operations like "Part" will still attempt to evaluate your value and may produce warnings.

Having the debugger set to "break on Messages" prevents an avalanche of errors, although it seems like an overkill to have it turned on all the time

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

焚却相思 2024-10-09 06:53:25

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

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

正如 Samsdram 指出的那样,正确输入函数将有助于很重要。不要忘记 模式,因为有时以这种形式表达某些模式更容易,例如x:{{_, _} ..}。显然,当这还不够时 PatternTest (?) 和 条件 (/;) 是可行的方法。 Samdram 很好地涵盖了这一点,但我想补充一点,您可以通过纯函数创建自己的模式测试,例如 f[x_?(Head[#]===List&)] 是相当于f[x_List]。请注意,使用纯函数的&符号形式时,括号是必需的。

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

ThrowCatch 有它们的位置,但我觉得它们应该只在内部使用,并且您的代码应该通过 消息 设施。可以按照与设置使用消息相同的方式创建消息。我相信可以使用函数 检查, CheckAbort, AbortProtect

示例

我的代码中的一个示例是 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指出如何使用文件<定义检查的阅读器时, /code> 不是 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}

As others have pointed out, there are three ways to deal with errors in a consistent manner:

  1. correctly typing parameters and setting up conditions under which your functions will run,
  2. dealing correctly and consistently with errors generated, and
  3. simplifying your methodology to apply these steps.

As Samsdram pointed out, correctly typing your functions will help a great deal. Don't forget about the : form of Pattern as it is sometimes easier to express some patterns in this form, e.g. x:{{_, _} ..}. Obviously, when that isn't sufficient PatternTests (?) and Conditions (/;) are the way to go. Samdram covers that pretty well, but I'd like to add that you can create your own pattern test via pure functions, e.g. f[x_?(Head[#]===List&)] is equivalent to f[x_List]. Note, the parentheses are necessary when using the ampersand form of pure functions.

The simplest way to deal with errors generated is obviously Off, or more locally Quiet. For the most part, we can all agree that it is a bad idea to completely shut off the messages we don't want, but Quiet can be extremely useful when you know you are doing something that will generate complaints, but is otherwise correct.

Throw and Catch have their place, but I feel they should only be used internally, and your code should communicate errors via the Message facilities. Messages can be created in the same manner as setting up a usage message. I believe the key to a coherent error strategy can be built using the functions Check, CheckAbort, AbortProtect.

Example

An example from my code is OpenAndRead which protects against leaving open streams when aborting a read operation, as follows:

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

which, Until recently, has the usage

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

However, this is annoying to do every time.

This is where belisarius solution comes into play, by creating a method that you can use consistently. Unfortunately, his solution has a fatal flaw: you lose support of the syntax highlighting facilities. So, here's an alternative that I came up with for hooking into OpenAndRead from above

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

which has usage

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

Now, checking the definition of myReader gives two definitions, like we want. In the function body, though, file must be referred to as file$. (I have not yet figured out how to name the file var as I'd wish.)

Edit: MakeCheckedReader works by not actually doing anything itself. Instead, the TagSet (/:) specification tells Mathematica that when MakeCheckedReader is found on the LHS of a SetDelayed then replace it with the desired function definitions. Also, note the use of Quiet; otherwise, it would complain about the patterns a_ and b_ appearing on the right side of the equation.

Edit 2: Leonid pointed out how to be able to use file not file$ when defining a checked reader. The updated solution is as follows:

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

The reasoning for the change is explained in this answer of his. Defining myReader, as above, and checking its definition, we get

myReader[file$_String,a_,b_]:=OpenAndRead[file$,myReader[#1,a_,b_]&]
myReader[file_Symbol,a_,b_]:={file,a,b}
写下不归期 2024-10-09 06:53:25

我来晚了,带着一个被接受的答案和所有的东西,但我想指出形式的定义:

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 会完全退出 - 通过添加 / 的简单权宜之计即可实现;最终表达式的条件。

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

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

I'm coming late to the party, with an accepted answer and all, but I want to point out that definitions of the form:

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

are very useful in this context. Definitions of this kind can perform complex calculations before finally bailing out and deciding that the definition was not applicable after all.

I will illustrate how this can be used to implement various error-handling strategies in the context of a specific case from another SO question. The problem is to search a fixed list of pairs:

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

to find the first pair whose second component is greater than or equal to a specified value. Once that pair is found, its first component is to be returned. There are lots of ways to write this in Mathematica, but here is one:

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

f0[100] (* returns 8 *)

The question, now, is what happens if the function is called with a value that cannot be found?

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

The error message is cryptic, at best, offering no clues as to what the problem is. If this function was called deep in a call chain, then a cascade of similarly opaque errors is likely to occur.

There are various strategies to deal with such exceptional cases. One is to change the return value so that a success case can be distinguished from a failure case:

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

f1[100] (* returns {8} *)
f1[1000] (* returns {} *)

However, there is a strong Mathematica tradition to leave the original expression unmodified whenever a function is evaluated with arguments outside of its domain. This is where the Module[... /; ...] pattern can help out:

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

Note that the f2 bails out completely if the final result is the empty list and the original expression is returned unevaluated -- achieved by the simple expedient of adding a /; condition to the final expression.

One might decide to issue a meaningful warning if the "not found" case occurs:

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

With this change the same values will be returned, but a warning message will be issued in the "not found" case. The Null return value in the new definition can be anything -- it is not used.

One might further decide that the "not found" case just cannot occur at all except in the case of buggy client code. In that case, one should cause the computation to abort:

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

In conclusion, these patterns are easy enough to apply so that one can deal with function arguments that are outside the defined domain. When defining functions, it pays to take a few moments to decide how to handle domain errors. It pays in reduced debugging time. After all, virtually all functions are partial functions in Mathematica. Consider: a function might be called with a string, an image, a song or roving swarms of nanobots (in Mathematica 9, maybe).

A final cautionary note... I should point out that when defining and redefining functions using multiple definitions, it is very easy to get unexpected results due to "left over" definitions. As a general principle, I highly recommend preceding multiply-defined functions with Clear:

Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...
破晓 2024-10-09 06:53:25

这里的问题本质上是其中一种类型。一个函数会产生错误的输出(类型不正确),然后将其输入到许多后续函数中,从而产生大量错误。虽然 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

The problem here is essentially one of types. One function produces a bad output (incorrect type) which is then fed into many subsequent functions producing lots of errors. While Mathematica doesn't have user defined types like in other languages, you can do pattern matching on function arguments without too much work. If the match fails the function doesn't evaluate and thus doesn't beep with errors. The key piece of syntax is "/;" which goes at the end of some code and is followed by the test. Some example code (and output is below).

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

Output:
2
Average[$Failed]

If the test is simpler, there is another symbol that does similar pattern testing "?" and goes right after an argument in a pattern/function declaration. Another example is below.

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

Output:
square[{1, 2, 3}]
9
迷路的信 2024-10-09 06:53:25

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

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

It can help to define a catchall definition to pick up error conditions and report it in a meaningful way:

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

So your top level calls can use Catch[], or you can just let it bubble up:

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]]}]]
濫情▎り 2024-10-09 06:53:25

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

这是一个尝试:

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

What I'd love to get is a way to define a general procedure to catch error propagation without the need to change radically the way I write functions right now, preferentially without adding substantial typing.

Here is a try:

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] 

The :c-: is of course Esc c- Esc, an unused symbol (\[CircleMinus]), but anyone would do.

Output:

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

What we changed is

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

by

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

This almost satisfies my premises.

Edit

Perhaps it's also convenient to add a "nude" definition for the function, that does not undergo the error checking. We may change the funcDef rule to:

funcDef = 
     t_[args___]  \[CircleMinus] a_ :> 

            {t["nude", args] := a, 

             ReleaseHold[Hold[t[args] := Check[a, Print@Hold[a]; Abort[]]]]
            };  

to get for

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

this output

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

v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文