为什么在编写R包时,使用message()比print()更好?

115

我希望了解为什么在打印诊断信息时,message()print()更好。

例如,print()函数在打印R对象(如'iris')时是更好的选择,而当我们想要连接字符串时,message()更好,例如message("a", "b")print(paste0("a", "b"))更短。

然而,我认为它们之间的区别不仅仅限于上面列出的这些简单的差异。我已经阅读了两种方法的文档

但是,似乎它们对我的问题的解释并不如我所希望的那样详尽。

如果有人能告诉我们在哪种情况下message()print()更好,并说明原因,我将不胜感激。


2
@MartinMorgan,我基本上同意这一点,但是message()也会发出“消息”,这就是suppressMessages()捕获的内容。suppressMessages()不会抑制纯stderr输出,例如suppressMessages(cat("hello\n", file=stderr()))仍然会在控制台中显示hello - HenrikB
@HenrikB stopwarningmessage 都是信号条件,这也是它们可以被捕获/抑制的原因。例如:tryCatch(message("hello"), message=force);如果意图是发出诊断条件信号,则使用 cat(file=stderr()) 是不好的风格(而且无效,正如你的示例所说明的那样!)。 - Martin Morgan
1个回答

200

TL;DR

当为S3对象制作print.*()函数时,应使用cat()。对于其他所有情况,请使用message(),除非程序的状态有问题。例如,可恢复的错误使用warning(),而致命错误使用stop()

目标

本文的目的是提供反馈,介绍包开发人员可以访问的不同输出选项以及如何构造基于新对象或基于字符串的输出。

R输出概述

传统的输出函数有:
  1. print()(打印)
  2. cat()(连接)
  3. message()(信息)
  4. warning()(警告)
  5. stop()(停止)
现在,前两个函数(print()cat())将它们的输出发送到stdout或标准输出。而最后三个函数(message(), warning()stop())将它们的输出发送到stderr或标准错误。也就是说,像lm()这样的命令的结果输出被发送到一个文件,而错误输出(如果存在)则被发送到完全不同的文件。这对用户体验特别重要,因为诊断信息不会混杂在日志文件中的结果输出中,而错误信息可以快速搜索。

为用户和外部包设计

现在,上述内容更多地以I/O思维为基础,而不是面向用户的框架。因此,让我们在日常R用户的背景下提供一些动机。特别是,通过使用3-5个或stderr函数,它们的输出可以被抑制,而不需要通过sink()capture.output()来调整控制台文本。抑制通常以suppressWarnings(), suppressMessages(), suppressPackageStartupMessages()等形式出现。因此,用户只会面对结果导向的输出。如果您计划允许用户在创建动态文档时关闭基于文本的输出,这一点尤其重要,可以使用knitr, rmarkdown, 或Sweave
特别是,knitr提供了一些代码块选项,如error = Fmessage = Fwarning = F。这使得文档中伴随命令的文本减少了。此外,这也避免了使用results = "hide"选项来禁用所有输出的需要。

输出细节

print()

首先,我们有一个老牌但好用的函数print()。该函数有一些严重的限制。其中之一是缺乏嵌入式术语连接。第二个,可能更严重的是,每个输出都以[x]开头,后面跟着实际内容周围的引号。这里的x指的是正在打印的元素编号。这对于调试很有帮助,但在此之外没有任何作用。

例如:

print("Hello!")

[1] "Hello!"

对于字符串拼接,我们依赖于paste()函数与print()同步工作:

print(paste("Hello","World!"))

[1] "Hello World!"

另外,可以使用paste0(...)函数代替paste(...)函数,以避免paste()函数的sep = " "参数默认使用空格在元素之间进行连接。即不带空格的连接。

例如:

print(paste0("Hello","World!"))

[1] "HelloWorld!"

print(paste("Hello","World!", sep = ""))

[1] "HelloWorld!"

cat()

另一方面,cat() 解决了所有这些批评。最值得注意的是,cat() 函数内置了 paste() 功能的 sep=" " 参数,使您可以跳过编写 paste() 而直接使用它。然而,cat() 函数的唯一缺点是您必须通过在结尾添加 \n 或使用 fill = TRUE(使用默认打印宽度)来强制换行。

例如:

cat("Hello!\n")
Hello!

cat("Hello","World!\n")
Hello World!

cat("Hello","World!\n", sep = "")
HelloWorld!

正是因为这个原因,设计print.*() S3方法时应该使用cat()

message()

message()函数比cat()更好!原因是输出与传统纯文本不同,它被定向到 stderr 而不是 stdout 。例如:他们将颜色从标准黑色输出更改为红色输出,以吸引用户的注意。

Message Output

此外,您还可以使用内置的paste0()功能。
message("Hello ","World!") # Note the space after Hello
"Hello World!"

此外,message() 还提供了一个错误状态,可与 tryCatch() 一起使用。
例如:
 tryCatch(message("hello\n"), message=function(e){cat("goodbye\n")})
 goodbye

warning()

warning()函数不是随意使用的东西。warning函数与message函数的区别主要在于它有一行前缀("Warning message:")并且其状态被认为是有问题的。

warning output

Misc: 在函数中随意使用可能会在尝试将软件包上传到CRAN时不经意地触发心碎,因为示例检查和警告通常被视为“错误”。

stop()

最后,我们有stop()。它通过完全杀死正在进行的任务并将控制权交还给用户将警告提升到了更高的级别。此外,它具有最严重的前缀,术语"Error:"被添加。

Error Output


1
@antonio:message()将显示红色文本,而另一个则不会。现在,如果您向cat添加变量,例如cat("hello world", "antonio", sep="\n"),差异将是一行显示hello world,另一行显示antonio。但是,message()将保持所有内容在同一行上。 - coatless
2
感谢您的热情回复。我正在寻找类似的东西,但我的问题被标记为“懒惰”:https://dev59.com/hZXfa4cB1Zd3GeqPbCx4 - burger
不错的文章。你的 print 示例展示了 print(paste0("Hello","World!")) 输出 [1] "Hello World!",但实际上应该是 [1] "HelloWorld!"(没有空格 - 正如你在解释中所述)。 - mark
1
关于 write 的问题 https://dev59.com/vnNA5IYBdhLWcg3wC5Xh - sjd
1
“诊断”输出是指例如“在信息级别”打印的内容。不幸的是,message()cat()中不存在这个概念。我查看了R是否有成熟的日志记录接口,就像大多数系统今天所使用的那样(例如Java的slf4j API),并且确实有:futile.logger - 在CRAN上 - David Tonhofer
显示剩余7条评论

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