【问题标题】:R tryCatch but retain the expression result in the case of a warningR tryCatch 但在出现警告的情况下保留表达式结果
【发布时间】:2021-06-22 13:47:27
【问题描述】:

我有一个长时间运行的函数,在某些情况下可能会产生警告。 当它发生时,我想保留函数的结果,但在结果中添加一些诊断信息。

类似的东西

x = tryCatch(
    someLongRunningFunctionThatMightGenerateWarnings(),
    warning = function(w){
        c(w$exprResult, list(diagnostics = "some useful info"))
    }
)

有没有什么方法可以使用tryCatch 完成此操作,而无需在警告处理程序中第二次评估表达式?

【问题讨论】:

  • 我相当缺乏经验,所以对此持保留态度,但我过去也遇到过类似的问题。我能找到的唯一解决方案是将withCallingHandlers()(它将附加诊断信息用于警告,但将stop() 用于错误)作为expr 内的tryCatch()(仅用于stop() 用于错误)。由于function(w){...}warning 的匿名函数)的范围有限,我不得不使用全局函数将诊断结果附加到全局变量;或者更一般地说,我不得不在function(w){...} 之外的环境中调用函数和修改变量。
  • @Greg 我可以看到它是如何工作的。我通常不喜欢编写依赖于改变全局值的代码,但在紧要关头,这将是一个解决方案。希望有一个替代方案。
  • 也许您可以通过par <- parent.env(); par$var <- c(par$var, info_to_append) 简单地改变作为匿名函数父级的环境中的变量。
  • 有趣的想法
  • 后者因为它导致对结果的统一访问

标签: r error-handling


【解决方案1】:

这是一个带有自定义函数myCatch()base 解决方案,其形式类似于tryCatch()(与withCallingHandlers() 相同)。随意调整它,尤其是在我的# ADAPT... cmets 指定的区域。

request,我还更新 myCatch() 以接受用户定义的函数custom_fun。基于来自exprresultcustom_fun 将处理评估expr 时抛出的任何警告对象,其输出将作为diagnostics(与results 一起)返回。

myCatch <- function(# The expression to execute.
                    expr,
                    # Further arguments to tryCatch().
                    ...,
                    # User-defined function to extract diagnostic info from
                    # warning object, based on output that resulted from expr.
                    custom_fun = function(result, w){return(w)}) {
  ######################
  ## Default Settings ##
  ######################
  
  # Defaults to NULL results and empty list of diagnostics.
  DEFAULT_RESULTS <- NULL
  DEFAULT_DIAGNOSTICS <- NULL
  
  # Defaults to standard R error message, rather than a ponderous traceback
  # through the error handling stacks themselves; also returns the error object
  # itself as the results.
  DEFAULT_ERROR <- function(e){
    message("Error in ", deparse(e$call), " : ", e$message)
    return(e)
  }
  
  
  ################
  ## Initialize ##
  ################
  
  # Initialize output to default settings.
  res <- DEFAULT_RESULTS
  diag <- DEFAULT_DIAGNOSTICS
  err <- DEFAULT_ERROR
  
  # Adjust error handling if specified by user.
  if("error" %in% names(list(...))) {
    err <- list(...)$error
  }
  
  
  #######################
  ## Handle Expression ##
  #######################
  
  res <- tryCatch(
    expr = {
      withCallingHandlers(
        expr = expr,
        # If expression throws a warning, record diagnostics without halting,
        # so as to store the result of the expression.
        warning = function(w){
          parent <- parent.env(environment())
          parent$diag <- w
        }
      )
    },
    error = err,
    ...
  )
  
  
  ############
  ## Output ##
  ############
  
  # Package the results as desired.
  return(list(result = res,
              diagnostics = custom_fun(res, diag)))
}

应用

出于您的目的,请像这样使用myCatch()

x <- myCatch(someLongRunningFunctionThatMightGenerateWarnings())

或更一般地

x <- myCatch(expr = {
               # ...
               # Related code.
               # ...
               someLongRunningFunctionThatMightGenerateWarnings()
             },
             # ...
             # Further arguments like 'finally' to tryCatch().
             # ...
             custom_fun = function(result, w){
                                             # ...
                                             # Extract warning info from 'w'.
                                             # ...
                                             })

您可以随意自定义errorfinally,就像使用tryCatch() 一样。如果您进行自定义warning,您的diagnostics 仍将保留在输出中,但您将丢失result 的预期输出(这将成为您在@ 中指定的返回值987654347@).

如果我们按照您的具体示例here,并像这样使用myCatch()

output <- myCatch(
  log(-5),
  custom_fun = function(result, w){paste(as.character(result), "with warning", w$message)}
)
output 

然后 R 会显示警告信息

Warning message:
In log(-5) : NaNs produced

并给我们以下output

$result
[1] NaN

$diagnostics
[1] "NaN with warning NaNs produced"

更多示例

当我们将myCatch() 应用到某个示例expressions 时,只使用custom_fun 的默认值,结果如下:

正常

output_1 <- myCatch(expr = {log(2)},
                    finally = {message("This is just like using 'finally' for tryCatch().")})
output_1

将显示自定义消息

This is just like using 'finally' for tryCatch().

并给我们输出:

$result
[1] 0.6931472

$diagnostics
NULL

警告

output_2 <- myCatch(expr = {log(-1)})
output_2

将显示警告信息

Warning message:
In log(-1) : NaNs produced

并给我们输出:

$result
[1] NaN

$diagnostics
<simpleWarning in log(-1): NaNs produced>

错误(默认)

output_3 <- myCatch(expr = {log("-1")})
output_3

将优雅地处理错误并显示其消息

Error in log("-1") : non-numeric argument to mathematical function

仍然给我们输出(带有results的错误对象):

$result
<simpleError in log("-1"): non-numeric argument to mathematical function>

$diagnostics
NULL

错误(自定义)

output_4 <- myCatch(expr = {log("-1")}, error = function(e){stop(e)})
output_4

将杀死myCatch()并立即抛出错误,并通过myCatch()内的处理函数(此处为tryCatch())进行繁琐的回溯:

Error in log("-1") : non-numeric argument to mathematical function 

  6. stop(e) 
  5. value[[3L]](cond) 
  4. tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), 
         names[nh], parentenv, handlers[[nh]]) 
  3. tryCatchList(expr, classes, parentenv, handlers) 
  2. tryCatch(expr = {
         withCallingHandlers(expr = expr, warning = function(w) {
             parent <- parent.env(environment())
             parent$diag <- w ... 
  1. myCatch(expr = {
         log("-1")
     }, error = function(e) {
         stop(e) ...

由于myCatch() 被中断,它returns 没有值可以存储在output_ 中,这给我们留下了

Error: object 'output_4' not found

【讨论】:

  • 非常好。我想建议改变。在我的用例中,我希望我的诊断(部分)成为长期运行函数结果的函数。所以我想改变withCallingHandlers的警告部分,只设置一个产生警告的标志,然后在返回输出之前,应用一个带有参数wresult的用户定义函数,并将该输出分配给诊断。
  • 有趣...你能举个例子吗?
  • 我想你可以在myCatch() 的开头设置DEFAULT_DIAGNOSTICS &lt;- NULL,然后在内部warning = function(w){...} 中简单地做parent$diag &lt;- w(而不是parent$diag &lt;- list("some useful info", w$message))。这样做会将condition 对象(这里也是warningsimpleWarning)存储在diag 中,以便使用您的用户定义函数my_fun 进行任何计算,最终以return(list(results = res, diagnostics = my_fun(diag, res)) 结束。如果您希望my_fun 是动态的,我们甚至可以将其设为参数!
【解决方案2】:

不知道为什么我不能让它使用参数名称warning 而不是mywarning 并且不知道为什么它仍然打印警告消息,即使处理了警告,但这有助于证明想法。

myCatch <- function(# The expression to execute.
  expr,
  # Further arguments to tryCatch().
  ...) {
  ######################
  ## Default Settings ##
  ######################
  
  # Defaults to NULL results and empty list of diagnostics.
  DEFAULT_RESULTS <- NULL
  DEFAULT_DIAGNOSTICS <- NULL
  
  # Defaults to standard R error message, rather than a ponderous traceback
  # through the error handling stacks themselves; also returns the error object
  # itself as the results.
  DEFAULT_ERROR <- function(e){
    message("Error in ", deparse(e$call), " : ", e$message)
    return(e)
  }
  
  DEFAULT_WARNING <- function(result,w){
    w
  }
  ################
  ## Initialize ##
  ################
  
  # Initialize output to default settings.
  res <- DEFAULT_RESULTS
  diag <- DEFAULT_DIAGNOSTICS
  err <- DEFAULT_ERROR
  warn <- DEFAULT_WARNING
  
  # Adjust error handling if specified by user.
  if("error" %in% names(list(...))) {
    err <- list(...)$error
  }
  
  if("mywarning" %in% names(list(...))){
    warn <- list(...)$mywarning
  }
  
  #######################
  ## Handle Expression ##
  #######################
  
  res <- tryCatch(
    expr = {
      withCallingHandlers(
        expr = expr,
        
        ###################################################################################
        ######### ADAPT the code STARTING HERE. ###########################################
        ###################################################################################
        
        # If expression throws a warning, record diagnostics without halting,
        # so as to store the result of the expression.
        warning = function(w){
          parent <- parent.env(environment())
          parent$warning_arg <- w
        }
        
        ###################################################################################
        ######### ADAPT the code ENDING HERE. #############################################
        ###################################################################################
        
      )
    },
    error = err,
    ...
  )
  
  ############
  ## Output ##
  ############
  if ("warning_arg" %in% ls()){
    diag <- warn(res, warning_arg)
  }
  # Package the results as desired.
  return(list(result = res,
              diagnostics = diag))
}


myCatch(
  log(-5),
  mywarning = function(result, w){paste(as.character(result), "with warning", w$message)}
  
)

【讨论】:

  • 你可能有点过于复杂了。就从我的回答开始吧。然后,在函数声明中,只需将mywarning 作为函数参数:myCatch &lt;- function(expr, ..., mywarning = function(result, w){return(w)})。只需在内部warning 中执行parent$diag &lt;- w。最后,做return(result = res, diagnostics = mywarning(res, diag))。就这样!仅供参考,我求助于 if("error" %in% names(list(...))) {err &lt;- list(...)$error} 的唯一原因是因为 R 不会像对待 exprmywarning 这样的普通参数那样对待 ... 中的参数。
  • 哦,别忘了还有DEFAULT_DIAGNOSTICS &lt;- NULL
  • 我刚刚更新了我的原始答案以反映您想要的更改。
猜你喜欢
  • 1970-01-01
  • 2017-09-08
  • 2016-06-17
  • 1970-01-01
  • 2023-03-06
  • 2012-09-02
  • 1970-01-01
  • 1970-01-01
  • 2015-09-21
相关资源
最近更新 更多