提问人:Andreï V. Kostyrka 提问时间:3/23/2023 最后编辑:Andreï V. Kostyrka 更新时间:3/24/2023 访问量:67
在 R 中捕获警告,而无需重复计算或文件连接
Catching a warning without repeated evaluation or file connections in R
问:
假设有一个挑剔的函数,其评估成本很高。它可能是一种优化算法,如果它终止时没有错误但未通过某些检查(例如,达到.max.迭代),则可能会引发警告。我想保存输出和警告(如果有),因为这些警告包含有价值的信息。f()
f()
这些解决方案依赖于第二次调用函数或使用 。坦率地说,网上的文档或示例太少了,我无法通过查看定义来理解这个函数的作用。withCallingHandlers
withCallingHandlers
该解决方案依赖于创建一个临时文件,然后从中读取行。问题是,我试图从数百万次调用中捕获警告(函数内部外观的小优化问题,它被调用数百万次,如果不是数十亿次),因此每次创建一次可能是不稳定的(这些函数是并行调用的)。purrr::capture_output()
file()
下面是一个 MWE:有一个慢速函数(1 秒),可能会或可能不会抛出警告,捕获警告需要第二次评估(下面的代码生成 2 个警告和 2 个无警告结果 = 2 + 2*2 = 6 秒):
f <- function(i) {
Sys.sleep(1)
if (i < 3) warning(paste0("i is too small, took ", sample(3:10, 1),
" iterations, code ", sample(2:5, 1), "."))
return(i)
}
set.seed(1)
f(1) # Warning
f(6) # No warning
saveWarnSlow <- function(i) {
tryCatch(f(i), warning = function(w) list(output = f(i), warning = w))
}
system.time(out <- lapply(1:4, saveWarnSlow)) # Runs for 6 seconds
sapply(out, function(x) if (length(x) == 1) "OK" else as.character(x$warning))
# [1] "simpleWarning in f(i): i is too small, took 9 iterations, code 2.\n"
# [2] "simpleWarning in f(i): i is too small, took 9 iterations, code 4.\n"
# [3] "OK"
# [4] "OK"
也许这是由于我对病情处理和恢复的了解不足,但是......ON如何摆脱重复的功能求值来保存警告?
答:
我认为你不能用 ,但你可以用 .这个想法是将警告消息保存在调用方可见的某个位置,然后将警告附加到结果中。tryCatch
withCallingHandlers
f <- function(i) {
Sys.sleep(1)
if (i < 3) warning(paste0("i is too small, took ", sample(3:10, 1),
" iterations, code ", sample(2:5, 1), "."))
return(i)
}
set.seed(1)
f(1) # Warning, but it won't be saved
#> Warning in f(1): i is too small, took 3 iterations, code 5.
#> [1] 1
f(6) # No warning
#> [1] 6
saveWarnSlow <- function(i) {
thewarning <- ""
wrapper <- function(value) {
# Lazy evaluation means value hasn't been computed yet.
thewarning <<- ""
list(value = value, warning = thewarning)
}
withCallingHandlers(wrapper(f(i)),
warning =
function(w) {
thewarning <<- conditionMessage(w)
invokeRestart("muffleWarning")
})
}
system.time(out <- lapply(1:4, saveWarnSlow)) # Runs for 4 seconds
#> user system elapsed
#> 0.013 0.000 4.024
out
#> [[1]]
#> [[1]]$value
#> [1] 1
#>
#> [[1]]$warning
#> [1] "i is too small, took 9 iterations, code 2."
#>
#>
#> [[2]]
#> [[2]]$value
#> [1] 2
#>
#> [[2]]$warning
#> [1] "i is too small, took 4 iterations, code 2."
#>
#>
#> [[3]]
#> [[3]]$value
#> [1] 3
#>
#> [[3]]$warning
#> [1] ""
#>
#>
#> [[4]]
#> [[4]]$value
#> [1] 4
#>
#> [[4]]$warning
#> [1] ""
创建于 2023-03-23,使用 reprex v2.0.2
我不确定如何回答您的问题,除非更详细地解释使用 and 的方法(您和@user2554330已经提到过)。withCallingHandlers
invokeRestart
在不失去普遍性的情况下,我们可以将注意力限制在 而不是 发出的警告上。后一种情况在概念上是相同的,但它涉及一层 C 代码(用于将消息组件粘贴在一起),这对于理解条件处理并不重要。warning(<condition>)
warning(<character>)
w.list <- list()
f <- function() {
w <- simpleWarning("hello", sys.call())
for (i in 1:3) warning(w)
0
}
h <- function(w) {
w.list <<- c(w.list, list(w))
invokeRestart("muffleWarning")
}
list(value = withCallingHandlers(f(), warning = h), warnings = w.list)
$value
[1] 0
$warnings
$warnings[[1]]
<simpleWarning in f(): hello>
$warnings[[2]]
<simpleWarning in f(): hello>
$warnings[[3]]
<simpleWarning in f(): hello>
withCallingHandlers
乍一看与.两者都将处理程序推送到处理程序堆栈上,计算表达式,并捕获它们为其找到处理程序的信号条件。当处理程序捕获条件时,在堆栈上清除该处理程序及其上方的处理程序,然后使用捕获的条件作为参数来评估对处理程序的调用。tryCatch
主要区别在于捕获条件时将控制权转移到的上下文。
当 建立的处理程序捕获条件时,控制权将转移到调用的上下文中。计算 halts,使用捕获条件作为参数调用处理程序,并返回此处理程序调用的值。如果不重新评估,就无法恢复评估,因为其第一次评估的背景已经“丢失”。tryCatch(expr, ...)
tryCatch
expr
tryCatch
expr
当 的处理程序捕获条件时,控件“保留”在发出条件信号的上下文中,在对 、 、 或 的调用中(在调用堆栈中的某个位置与 的计算相关联)。在 的情况下,该上下文就在这里,在这两个调用之间:withCallingHandlers(expr, ...)
signalCondition
message
warning
stop
expr
warning
.Internal
> body(warning)
## ... TRUNCATED ...
withRestarts({
.Internal(.signalCondition(cond, message, call))
.Internal(.dfltWarn(message, call))
}, muffleWarning = function() NULL)
invisible(message)
## ... TRUNCATED ...
使用捕获条件作为参数调用处理程序,但不返回此处理程序调用的值。处理程序返回后,将依次尝试任何剩余的处理程序,直到没有剩余的处理程序。最后,对简历进行评估并返回 的值。在 的情况下,使用上面的第二个调用评估简历,它打印警告消息和呼叫。withCallingHandlers
expr
withCallingHandlers
expr
warning
expr
.Internal
因此,在此示例中有两个目的:invokeRestart("muffleWarning")
- 它停止处理程序调用的序列,以便忽略 建立的任何剩余处理程序。(嗯,这里没有。
withCallingHandlers
- 它“跳过”上面的第二个调用,因此不会打印警告消息和调用。
.Internal
它通过将控制权转移到建立处理程序的调用来实现此目的。 返回调用不带参数的处理程序的结果(即 ),返回 和 继续的计算结果。withRestarts
muffleWarning
withRestarts
muffleWarning
NULL
warning
invisible(message)
expr
如果更清楚地记录 and 处理程序,那就太好了。目前,您必须查看 的主体,并查看它们是否存在并且它们只是返回 .help("conditions")
muffleMessage
muffleWarning
message
warning
NULL
评论