是否有一种好方法在R中只警告一次?
我现在做的是常规的
a_reason_to_warn_has_occured <- FALSE
lapply(data, function(data) {
result <- do_something(data)
if (warning_reason)
a_reason_to_warn_has_occured <- TRUE
result
})
if (a_reason_to_warn_has_occured)
warning("This was bad.")
是否有一种方法可以用更少的混乱/锅炉板代码做到这一点?
我真的很喜欢这样的东西
lapply(data, function(data) {
result <- do_something(data)
warn_once_if(warning_reason, "This was bad.")
result
})
但我不确定是否有可能在r中实现这个
备注
我认为你的解决方案很好,我可能会在生产代码中使用它。然而,如果你对另一种更酷但可能更脆弱的方式感兴趣,请继续往下读。
使用非标准求值的解决方案
当然可以创建一个函数,它接受一个表达式,并对其求值,并且只对每个原因发出一次警告。你可以这样使用:
warn_once(
lapply(data, function(data) {
result <- doSomething(data)
warn_if_first(reason = "bad data argument", message = "This was bad.")
result
})
)
也可以采用您建议的形式,但是设置只需要一个警告的范围是很棘手的。请看这两个例子。第一个是原始代码。
lapply(data, function(data) {
result <- doSomething(data)
warn_if_first(warningReason, "This was bad.")
result
})
这很简单。您希望每个外部lapply
块都有一个警告。但是如果你有下面这个:
lapply(data, function(data) {
result <- doSomething(data)
sapply(result, function(x) {
warn_if_first(warningReason, "This was bad.")
})
result
})
然后(至少与warn_if_first
的直接实现),您将获得每个sapply
调用一个警告,并且没有简单的方法告诉warn_if_first
,如果您想要每个lapply
调用一个警告。
所以我建议使用上面的表单,它显式地指定您将获得单个警告的环境。
实施warn_once <- function(..., asis = FALSE) {
.warnings_seen <- character()
if (asis) {
exprs <- list(...)
} else {
exprs <- c(as.list(match.call(expand.dots = FALSE)$...))
}
sapply(exprs, eval, envir = parent.frame())
}
warn_if_first <- function(reason, ...) {
## Look for .warnings_seen
for (i in sys.nframe():0) {
warn_env <- parent.frame(i)
found_it <- exists(".warnings_seen", warn_env)
if (found_it) { break }
}
if (!found_it) { stop("'warn_if_first not inside 'warn_once'") }
## Warn if first, and mark the reason
.warnings_seen <- get(".warnings_seen", warn_env)
if (! reason %in% .warnings_seen) {
warning(...)
.warnings_seen <- c(.warnings_seen, reason)
assign(".warnings_seen", .warnings_seen, warn_env)
}
}
让我们试试!
warn_once({
for (i in 1:10) { warn_if_first("foo", "oh, no! foo!") }
for (i in 1:10) { warn_if_first("bar", "oh, no! bar!") }
sapply(1:10, function(x) {
warn_if_first("foo", "oh, no! foo again! (not really)")
warn_if_first("foobar", "foobar, too!")
})
"DONE!"
})
输出[1] "DONE!"
Warning messages:
1: In warn_if_first("foo", "oh, no! foo!") : oh, no! foo!
2: In warn_if_first("bar", "oh, no! bar!") : oh, no! bar!
3: In warn_if_first("foobar", "foobar, too!") : foobar, too!
,这似乎是对的。一个小故障是警告即将到来的warn_if_first
,而不是从它的调用环境,因为它应该是,但我不知道如何解决这个问题。warning
也使用非标准求值,所以不像eval(warning(...), envir = parent.frame())
那样简单。您可以将call. = FALSE
提供给warning()
或warn_if_first()
,然后您将得到
[1] "DONE!"
Warning messages:
1: oh, no! foo!
2: oh, no! bar!
3: foobar, too!
可能更好。
谨慎虽然我没有看到这个实现有任何明显的问题,但我不能保证它在某些特殊情况下不会中断。非标准评估很容易犯错误。一些基本的R函数,还有一些流行的软件包,比如magrittr,也使用非标准的求值,那么你必须加倍小心,因为它们之间可能存在交互。
我用于记账的变量名.warnings_seen
足够特殊,因此它在大多数情况下不会干扰其他代码。如果你想(几乎)完全确定,生成一个长随机字符串,并使用它作为变量名。
关于范围的进一步阅读
- "R入门"中关于作用域的部分:http://cran.r-project.org/doc/manuals/R-intro.html#Scope
- Hadley Wickam的书中关于非标准评估的章节:http://cran.r-project.org/doc/manuals/R-intro.html#Scope,特别是关于动态范围的部分。
根据评论和Gabors的回答,以下是我尝试实现无作用域解决方案的结果。它基于比较对warn_once调用的回溯。请注意,这只是一个快速的草案,绝对不完美。有关详细信息,请参见下文。
warn_once <- function(mesg) {
trace <- traceback(0)
if (exists(".warnings_shown", sys.frame(1))) {
warn_list <- get(".warnings_shown", sys.frame(1))
found_match <- FALSE
for (warn in warn_list)
if (all(unlist(Map(`==`, warn, trace))))
return()
warn_list[[length(warn_list)+1]] <- trace
assign(".warnings_shown", warn_list, envir=sys.frame(1))
warning(mesg)
} else {
assign(".warnings_shown", list(trace), envir=sys.frame(1))
warning(mesg)
}
}
作为测试用例,我使用了…
func <- function(x) {
func2(x)
func2(not(x))
func2(x)
func2(not(x))
}
func2 <- function(x) {
if(x) for(i in 1:3) warn_once("yeah")
if(not(x)) warn_once("nope")
warn_once("yeah")
}
func(T)
…这导致了…
Warning in warn_once("yeah") : yeah
Warning in warn_once("yeah") : yeah
Warning in warn_once("nope") : nope
Warning in warn_once("yeah") : yeah
Warning in warn_once("yeah") : yeah
Warning in warn_once("yeah") : yeah
Warning in warn_once("nope") : nope
Warning in warn_once("yeah") : yeah
…以及调用traceback时产生的大量杂乱输出。
指出:
- 我猜是有可能抑制
traceback()
调用的输出,但我不能这样做。 - 根据警告在帧栈中的位置来识别警告,而不是像Gabors的回答那样通过警告信息来识别警告。这可能是但不一定是理想的行为。
- 从回溯中,可以推断出调用函数的名称并将其添加到警告消息中,这可能是有用的。
- 显然,可以引入一个可选参数来指定在搜索"。warnings_show"时在帧堆栈中上升的级别数。
评论和改进(只要编辑!)欢迎。