Mathematica中的信息生成

7

在我写的一个模块中(用于开发和测试阶段),我有很多Print["Messages"]。我有两个问题:

  1. 打印这样的“调试”消息的最佳实践是什么?
  2. 有没有一种方法可以调用该模块,使得消息不会被打印出来?例如,在从另一个模块调用该模块时,我希望不看到第一个模块的所有输出。

2
在v.8中,还有一个类似的Assert,它会在测试失败时打印消息,并且可以被关闭。 - Yaroslav Bulatov
@Verbeia 是的,没错...我只是觉得这两个问题非常相似,将它们合并并在同一个地方拥有所有答案会更有益。无论是将那个合并到这个还是反过来都无所谓... - Szabolcs
@Szabolcs,聊天室里有留言给您。 - Verbeia
5个回答

12

我喜欢把全局变量的名称前缀加上$,而将函数命名时则不加前缀,因此我会这样写:

debugPrint[args___] := $debugPrintFunction[args]

$debugPrintFunction = Print[##] &

那么你可以像现在使用Print一样使用debugPrint。当你想要摆脱调试信息时,只需取消设置变量即可:

$debugPrintFunction = .

这种方式有一些优势。其中之一是您可以使用Block在本地切换调试开关:

In[1]:= foo[x_] := (debugPrint[x]; x+1)

In[2]:= foo[3]
3
Out[2]= 4

In[3]:= Block[{$debugPrintFunction}, foo[3]
Out[3]= 4

您甚至可以在本地使$debugPrintFunction执行其他操作,例如对于Reap要拾取的值进行Sow,或将调试消息重定向到其他位置,比如

strm = OpenWrite["your/log/path/here", InputForm];
Block[{$debugPrintFunction = Write[strm, ##]},
  foo[3]];
Close[strm];

如果明智地使用由Block提供的动态作用域,可以相对安全和可控地使用全局变量。


不错的技巧。我已经用完了投票,但是一旦袋子重新装满,我就会点赞。 - Dr. belisarius

8

我之前的代码使用了Pillsy描述的方法。

最近我使用了一个通常没有任何规则的头部,例如:

...
debugPrint[expr]
...

然后再有一个第二个函数:
Attributes[PrintDebug]={HoldAll}

PrintDebug[expr_] := Block[{debugPrint = Print}, expr]

那么当我想查看调试输出时,我可以将PrintDebug包装在输入周围:

PrintDebug[MyFunction[1,2,3]]

或者,更常见的用法是
MyFunction[1,2,3] // PrintDebug

我发现后缀形式更容易添加/删除,并且更好地保留主函数的焦点。


6

我通常会在我的函数中安装一个冗长选项,如果需要调试,则可以打开/关闭该选项。请注意,在函数内指定Verbose的默认值可以控制是否打印信息。

In[5]:= func1[arg_, opts___] := Module[{verbose},
   verbose = Verbose /. {opts} /. {Verbose -> True};
   If[verbose, Print["Verbosing function1: arg is ", arg]];
   arg
   ];

func2[arg_, opts___] := Module[{verbose},
   verbose = Verbose /. {opts} /. {Verbose -> False};
   func1[arg, Verbose -> verbose]
   ];

In[7]:= func1[123]

During evaluation of In[7]:= Verbosing function1: arg is 123

Out[7]= 123

In[8]:= func2[456]

Out[8]= 456

In[9]:= func1[123, Verbose -> False]

Out[9]= 123

In[10]:= func2[456, Verbose -> True]

During evaluation of In[10]:= Verbosing function1: arg is 456

Out[10]= 456

当然,可以将此示例详细说明以符合Mathematica的编程标准(例如添加Options [func1,Verbose -> ...]标题,然后从函数内部访问选项),但这不是重点。

1
你可能会发现以下构造也很有帮助:__verbose && Print["Verbosing function1: arg is ", arg]__,而不是使用IF语句。 - Dr. belisarius
这是一个聪明的快捷方式,省去了我很多括号匹配的麻烦,谢谢! - István Zachar

3
另一个可能性是:
debugPrint::msg = "Debug message: `1`";    
debugPrint[msg_] := Message[debugPrint::msg, msg]

使用如下函数:

debugPrint["hello"]

关闭或打开此类消息:

Off[debugPrint::msg]

On[debugPrint::msg]

0
对我来说,由于M没有内置调试器,我浪费了50%的时间在调试上,如果有调试器,这些时间本可以节省下来。我的开发代码中有50%是打印语句,因为没有这些语句,我会迷失在错误的来源中。(这也很糟糕,因为代码中太多的打印信息会使算法难以看清,它会妨碍视线,但不能删除它,因为我可能以后需要用到它)。
我发现这样一个强大而灵活的计算工具像M一样,仍然拥有相对较少先进的开发环境,这让我感到惊讶。当我使用Matlab时,使用调试器只需要几秒钟就能找到错误所在。
有人可能会说使用Workbench进行调试。我尝试使用它来调试一个Manipulate演示,但我无法理解。它太复杂了。M需要一个简单易用的内置调试器(在笔记本本身中,而不是一个单独的程序),并且带有行号!
好的,鉴于以上介绍:),这就是我对你的问题的回答。
  1. 有不同级别的调试信息。粗略级别和详细级别。粗略级别仅在进入函数和退出函数时打印一条消息。

  2. 我在用户界面上有一个按钮可以用来开启/关闭调试(如果您正在进行基于UI的程序,否则它将在代码中)。

  3. 使用单独的调试函数,调试消息会通过该函数进行输出。在此函数中,您可以在打印之前添加时间戳等内容(也可以控制是否要将消息从一个地方写入文本文件)。其余部分的代码只需使用该调试函数呼叫要打印的消息即可。我现在将所有内容都打印到控制台而不是当前笔记本。

  4. 每个调试消息都以调用它的函数名开头。

  5. 如果要在模块级别控制调试,我所做的就是简单地在该模块内部设置一个局部调试标志,并在每次想要调试该特定模块时开启/关闭该标志。这个局部调试标志接管了全局调试标志的设置。这样,如果需要,我可以仅调试一个模块,而不是整个代码。

有很多其他的方法来自定义所有这些。但我发现,如果我在一开始花更多的时间来制作一个良好的调试消息系统,那么当需要找到bug时,它会非常有帮助。

以下是一些有用的链接

http://reference.wolfram.com/mathematica/guide/TuningAndDebugging.html

工作台调试器(如果您知道如何使用它来调试Manipulate和Dynamics,请告诉我)

http://www.wolfram.com/products/workbench/features/debug.html


3
只是为了澄清,Mathematica确实有一个内置的调试器,您可以设置断点、步入代码等。虽然使用起来有点不方便,但它确实存在。Evaluation -> Debugger - Szabolcs
@Szabolcs,是的,我知道那个。但它真的实用吗?我前几天花了大约2个小时,甚至无法弄清楚如何使用它来调试我的笔记本Manipulate。最后,我放弃了,回到了打印消息 :) - Nasser
2
许多人可能会遇到这个问题和答案,我认为他们声称没有调试器是误导性的。虽然我确实不经常使用它,而且它并不是非常实用,但我偶尔会使用它。 - Szabolcs
1
在这里可以找到内置调试器用法的简明描述:https://dev59.com/7Gsy5IYBdhLWcg3wtwW9#8363520 - Sjoerd C. de Vries
@SjoerdC.deVries,谢谢你!我会再仔细看一下。我可能需要所有的调试帮助。 - Nasser
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接