R 中的打印是怎么工作的
R 有一个其他主流语言都没有的习惯:在控制台里,你根本不需要写打印语句。输入变量名,按下回车,R 就会打印它的值。这就是自动打印,也是 R 教程里到处都是单独表达式的原因。
但自动打印有一个严格的边界:它只对顶层的表达式生效。在 for 循环或函数体内部,单独的值会被求值,然后悄悄丢弃:
单独的 x 会被打印(它在顶层),第一个循环什么也不输出,第二个循环输出 1、2、3,因为它显式地要求了打印。“我的循环运行了,却什么都不显示”是 R 中被问得最多的问题之一,而答案就是这些:在循环和函数内部,打印永远是你自己的事。(用 Rscript 运行脚本时也是如此:在控制台逐行运行时有输出的代码,放进循环里可能就没有输出了。)
print():正式的那个
print() 按 R 看待一个值的方式来显示它,所以它适合用来查看数据,却不适合用来生成美观的输出:
有两点很显眼。字符串保留了引号:print() 展示的是忠实的表示形式,引号告诉你这是一个字符值。另外每一行都以 [1] 这样带方括号的数字开头:它是该行第一个元素的索引,所以当一个长向量换到新的一行时,每行的方括号会告诉你这一行从第几个元素接着开始。这是阅读向量的辅助信息,不是噪音。
如果你想要 print() 风格的输出但不要引号,print(x, quote = FALSE) 和 noquote(x) 都可以做到。不过到了这一步,你真正想要的通常是 cat()。
cat():给人看的输出
cat()(concatenate-and-print,连接并打印)输出原始文本:没有引号、没有索引前缀,各个参数之间用空格连接。获得这种控制的代价是它不会替你添加任何东西,包括换行符:
忘记写 "\n" 是每个人用 cat() 时都会经历的一课:没有它,下一段输出就会粘在同一行上。养成用 "\n" 结束 cat() 调用的习惯。
两个函数在引号上的差别,本质上是显示内容的差别。print() 按你输入的样子显示转义序列;cat() 则会把它们渲染出来:
print() 在引号里原样显示 \t 和 \n,当你要调试一个字符串里到底有什么时很有用。cat() 把它们变成真正的制表符和真正的换行,当你要生成输出时很有用。按用途来选:print() 用来查看,cat() 用来展示。
格式化报告:sprintf() + cat()
cat() 用空格分隔参数的方式能做的有限。要得到对齐、格式化的输出,比如固定的小数位、补齐宽度的计数器,就用 sprintf() 构造字符串,再交给 cat():
%-8s 把每个名字补齐到 8 个字符,%6.2f 让每个分数宽度相同并保留两位小数,于是各列就对齐了。这一组合是 base R 中输出进度信息和小型文本报告的主力(格式代码的用法见字符串)。
message() 与 warning():另一个通道
cat() 和 print() 写入标准输出,也就是存放程序结果的那条流。message() 和 warning() 写入标准错误,也就是用来说明运行情况的那条流:
在屏幕上两者看起来差不多,但一旦输出被重定向,这种区分就很重要了:把脚本的 stdout 重定向到文件,42 会进入文件,而状态说明仍然留在终端上。经验法则是:结果用 cat() 输出到 stdout,说明用 message() 输出到 stderr。这也是为什么函数和包要用 message()(而不是 cat())来和用户沟通才算得体:调用者可以用 suppressMessages() 让它安静下来,而不会丢掉真正的输出。
warning() 的语气更重,意思是“有点不对劲,但我继续执行了”。当脚本在 Rscript 下运行时,警告会被收集起来,在最后统一显示,而不是出现在中途(如何阅读它们见调试错误)。
读取用户输入:readline() 与 readLines()
要在交互式会话(R 控制台或 RStudio)中向用户提问,用 readline()。这些示例无法在嵌入的编辑器中运行,因为页面上的运行器没有输入面板,所以请在本地控制台里尝试:
# Interactive sessions only:
name <- readline("What is your name? ")
cat("Hello,", name, "\n")
age <- as.numeric(readline("Your age? "))
cat("Next year you'll be", age + 1, "\n")
有两点要记住。readline() 总是返回字符串:做数学运算之前要用 as.numeric() 转换,并检查结果是否为 NA,以防用户输入的不是数字。另外,在非交互式脚本中,readline() 根本不会等待输入,它会立刻返回 ""。
对于用 Rscript 运行、需要读取管道输入或键盘输入的脚本,要改为从标准输入读取:
# In a script run as: echo "Ada" | Rscript greet.R
line <- readLines(con = "stdin", n = 1)
cat("Hello,", line, "\n")
不带 n 的 readLines(con = "stdin") 会一直读取到输入结束,这是过滤器式脚本的标准写法。读取数据文件是另一回事,有更好的工具可用;当你的输入是数据集而不是键盘时,从 read-csv 开始。
要点回顾
- 自动打印只发生在顶层:在循环和函数内部,要自己调用
print()或cat()。 print()用来查看:字符串带引号,带[1]索引前缀,转义序列原样显示。cat()用来展示:原始文本,参数之间用空格连接,"\n"要你自己加。sprintf()+cat()是 base R 中生成格式化、对齐输出的常用写法。- 结果输出到 stdout(
cat),说明输出到 stderr(message、warning)。 - 交互式输入用
readline()(返回的永远是字符串!);Rscript 中用readLines(con = "stdin")。
下一篇:运算符,包括算术运算符、比较运算符,以及驱动你写下的每一个条件的逻辑运算符。
常见问题
R 语言中 print() 和 cat() 有什么区别?
print() 按 R 内部的表示方式显示一个值:字符串保留引号,每一行都带有 [1] 这样的索引前缀。cat() 输出原始文本,没有引号、没有索引,也不会自动换行(你要自己加 "\n")。查看对象用 print(),给人看的输出用 cat()。
为什么我的 R 循环什么都不输出?
自动打印只对顶层的表达式生效。在 for 循环或函数体内部,单独写一个 x 会被求值,然后悄悄丢弃。要显式地输出它:print(x) 或 cat(x, "\n")。
R 语言怎么读取用户输入?
在交互式会话中,readline("prompt: ") 把用户输入的一行读成字符串(如果需要数字,用 as.numeric() 转换)。在用 Rscript 运行的脚本中,readline() 不会等待输入,这时要改用 readLines(con = "stdin", n = 1) 从标准输入读取。
R 语言怎么输出不带引号的内容?
用 cat("hello\n"),它从来不会加引号。如果你确实想要 print() 的行为但不要引号,print("hello", quote = FALSE) 或 noquote("hello") 也可以。