你好,欢迎访问远方教程PC版!
广告位招租

R技巧[46]:程序异常或错误处理方法

[日期:2019-11-08]   来源:远方教程  作者:远方教程   阅读:20次[字体: ] 访问[旧版]
 捐赠远方教程 

        我们希望程序运行过程中,如果碰到一些可以预计的错误,可以自动处理它们,忽略这些异常,继续执行后面的代码,那么可以使用try、tryCatch、withCallingHandlers函数进行异常的处理,让程序继续往下执行。

1. 在R中,有三个函数工具可以解决条件异常处理(包括错误)问题:

  • try() 如果出现错误,使用该函数可以跳过错误继续执行程序。
  • tryCatch() 指定控制条件,进行异常捕捉,然后采用对应的函数处理异常和错误。
  • withCallingHandlers() 是tryCatch()的变体,只是运行的上下文条件不同,它使用的情况很少,但是非常有用。

2. 函数参数详解与示例

  • try()

    • R语言中的异常处理和Java类似,使用了try()语句来捕获异常,不过没有对应的catch()语句。
    • 在使用try()函数捕获异常后,再对捕获的对象进行解析。
    • try()函数第一个参数为调用的方法,第二个参数为是否显示异常消息,如 try(…, silent=TRUE)

      如果表达式运行产生错误提示,try()函数会返回一个类(class)对象'try-error'。如果参数 silent=TRUE,错误信息将被隐藏,silent=FALSE,错误信息将显示到屏幕上。在这种情况下,如果'try-error'的错误类型在变量 x.inv 的类型(class)中 ,我们调用 next 语句终止当前循环的执行,进行下一次的循环,否则,我们添加 x.inv 的值到表达式 inverses 。(示例见如下代码)

    1.  
      ###question1:###
    2.  
      ###求解逆矩阵过程中出错!!!怎么跳过错误!!!###
    3.  
      set.seed(1)
    4.  
      count <- 1
    5.  
      inverses <- vector(mode = "list", 100)
    6.  
      repeat {
    7.  
      x <- matrix(sample(0:2, 4, replace = T), 2, 2)
    8.  
      inverses[[count]] <- solve(x)
    9.  
      count <- count + 1
    10.  
      if (count > 100) break
    11.  
      }
    12.  
      ################
    13.  
       
    14.  
      ###answer1:#####
    15.  
      count <- 0
    16.  
      inverses <- vector(mode = "list", 100)
    17.  
      repeat {
    18.  
      if (count == 100) break
    19.  
      count <- count + 1
    20.  
      x <- matrix(sample(0:2, 4, replace = T), 2, 2)
    21.  
      x.inv <- try(solve(x), silent=TRUE)
    22.  
      if ('try-error' %in% class(x.inv)) {
    23.  
      next
    24.  
      } else{
    25.  
      inverses[[count]] <- x.inv
    26.  
      }
    27.  
      }
    28.  
      inverses
    29.  
      #inverses[!is.null(inverses)]
    30.  
      inverses[!(inverses=='NULL')]
    31.  
      ###############

      try() 允许出现错误后继续执行代码。例如,一般来说,如果你运行的函数引发错误,它会立即终止,并且不返回值:

    1.  
      f1 <- function(x) {
    2.  
      log(x)
    3.  
      10
    4.  
      }
    5.  
      f1("x")
    6.  
      #> Error in log(x): non-numeric argument to mathematical function

      但是,如果将产生错误的语句放在try()中,那么错误信息将被打印,但程序会继续执行:

    1.  
      f2 <- function(x) {
    2.  
      try(log(x))
    3.  
      10
    4.  
      }
    5.  
      f2("a")
    6.  
      #> Error in log(x) : non-numeric argument to mathematical function
    7.  
      #> [1] 10

      我们可以使用try(…, silent=TRUE)函数,隐藏错误异常信息。

      如果大段代码中有错误,想忽略错误,可以采用try(),但大段代码需放在{ }中:

    1.  
      #默认 silent = FALSE,显示错误信息
    2.  
      try({
    3.  
      a <- 1
    4.  
      b <- "x"
    5.  
      a + b
    6.  
      })
    7.  
       
    8.  
      #隐藏错误信息
    9.  
      try({
    10.  
      a <- 1
    11.  
      b <- "x"
    12.  
      a + b
    13.  
      } , silent = TRUE)

      你可以捕获try()的输出,如果程序运行成功,返回计算结果;如果程序运行不成功,则可以通过class()函数返回,错误类型 'try-error'。

    1.  
      success <- try(1 + 2)
    2.  
      failure <- try("a" + "b")
    3.  
      class(success)
    4.  
      #> [1] "numeric"
    5.  
      class(failure)
    6.  
      #> [1] "try-error"
    7.  
      ('try-error' %in% class(success))
    8.  
      #> [1] FALSE
    9.  
      ('try-error' %in% class(failure))
    10.  
      #> [1] TRUE

      在list列表中使用try()函数非常有用,可以有效避免个别元素不能计算引起的错误。

    1.  
      elements <- list(1:10, c(-1, 10), c(T, F), letters)
    2.  
      results <- lapply(elements, log)
    3.  
      #> Warning in FUN(X[[i]], ...): NaNs produced
    4.  
      #> Error in FUN(X[[i]], ...): non-numeric argument to mathematical function
    5.  
      results <- lapply(elements, function(x) try(log(x)))
    6.  
      #> Warning in log(x): NaNs produced

      在R中没有一个可以识别错误类型(class)-"try-error"的函数,我们可以自定义一个函数,然后结合sapply函数,可以非常方便的提取出错误类型、错误的位置以及错误值和正确值。

    1.  
      is.error <- function(x) inherits(x, "try-error")
    2.  
      succeeded <- !sapply(results, is.error)
    3.  
       
    4.  
      # look at successful results
    5.  
      str(results[succeeded])
    6.  
      #> List of 3
    7.  
      #> $ : num [1:10] 0 0.693 1.099 1.386 1.609 ...
    8.  
      #> $ : num [1:2] NaN 2.3
    9.  
      #> $ : num [1:2] 0 -Inf
    10.  
       
    11.  
      # look at inputs that failed
    12.  
      str(elements[!succeeded])
    13.  
      #> List of 1
    14.  
      #> $ : chr [1:26] "a" "b" "c" "d" ...

      try()一个非常实用的用法,如下:

    1.  
      default <- NULL
    2.  
      try(default <- read.csv("possibly-bad-input.csv"), silent = TRUE)
  • tryCatch()

      下面就是tryCatch()函数的标准语法:

    1.  
      result = tryCatch({
    2.  
      #正常的逻辑
    3.  
      expr
    4.  
      }, warning = function(w) {
    5.  
      #出现warning的处理逻辑
    6.  
      warning-handler-code
    7.  
      }, error = function(e) {
    8.  
      #出现error的处理逻辑
    9.  
      error-handler-code
    10.  
      }, finally = {
    11.  
      #不管出现异常还是正常都会执行的代码模块,
    12.  
      #一般用来处理清理操作,例如关闭连接资源等。
    13.  
      cleanup-code
    14.  
      }

      两个实际的小例子,code

    1.  
      #code1:
    2.  
      get.msg <- function(path)
    3.  
      {
    4.  
      con <- file(path, open = "rt", encoding = "latin1")
    5.  
      text <- readLines(con)
    6.  
      msg <- tryCatch({
    7.  
      text[seq(which(text == "")[1] + 1, length(text), 1)]
    8.  
      }, error = function(e) {
    9.  
      ""
    10.  
      })
    11.  
      close(con)
    12.  
      return(paste(msg, collapse = "\n"))
    13.  
      }
    14.  
       
    15.  
      #code2:
    16.  
      library(RMySQL)
    17.  
      result = tryCatch({
    18.  
      #获取数据连接
    19.  
      connect <- dbConnect(MySQL(), dbname="db_olap_web", username="root", password="")
    20.  
      #处理其他逻辑
    21.  
      #……
    22.  
      }, warning = function(w) {
    23.  
      #这里我只是简单处理一下
    24.  
      #也就是打印到控制台
    25.  
      print(w)
    26.  
      }, error = function(e) {
    27.  
      #这里我只是简单处理一下
    28.  
      #也就是打印到控制台
    29.  
      print(e)
    30.  
      }, finally = {
    31.  
      #关闭数据库连接
    32.  
      dbDisconnect(connect)
    33.  
      }

      使用tryCatch()函数,根据获取到的条件信号,返回相应的内置函数处理结果,错误、警告、消息等。

    1.  
      show_condition <- function(code) {
    2.  
      tryCatch(code,
    3.  
      error = function(c) "error",
    4.  
      warning = function(c) "warning",
    5.  
      message = function(c) "message"
    6.  
      )
    7.  
      }
    8.  
      show_condition(stop("!"))
    9.  
      #> [1] "error"
    10.  
      show_condition(warning("?!"))
    11.  
      #> [1] "warning"
    12.  
      show_condition(message("?"))
    13.  
      #> [1] "message"
    14.  
       
    15.  
      # If no condition is captured, tryCatch returns the
    16.  
      # value of the input
    17.  
      show_condition(10)
    18.  
      #> [1] 10

      我们可以使用tryCatch()函数来实现的try()函数的功能。需要使用conditionMessage()来提取与原来错误相关联的消息。

    1.  
      try2 <- function(code, silent = FALSE) {
    2.  
      tryCatch(code, error = function(c) {
    3.  
      msg <- conditionMessage(c)
    4.  
      if (!silent) message(c)
    5.  
      invisible(structure(msg, class = "try-error"))
    6.  
      })
    7.  
      }
    8.  
       
    9.  
      try2(1)
    10.  
      #> [1] 1
    11.  
      try2(stop("Hi"))
    12.  
      try2(stop("Hi"), silent = TRUE)

      当返回的错误值信号有缺省值时,但这是我们希望看到更加细节的错误信息,这是就需要我们自己封装一个tryCatch()函数过程,修改错误信息对象,来存储更多的错误信息。下面这个例子是,封装read.csv()函数的错误,将路径名称加到错误信息中!!!

    1.  
      read.csv2 <- function(file, ...) {
    2.  
      tryCatch(read.csv(file, ...), error = function(c) {
    3.  
      c$message <- paste0(c$message, " (in ", file, ")")
    4.  
      stop(c)
    5.  
      })
    6.  
      }
    7.  
      read.csv("code/dummy.csv")
    8.  
      #> Error in file(file, "rt"): cannot open the connection
    9.  
      read.csv2("code/dummy.csv")
    10.  
      #> Error in file(file, "rt"): cannot open the connection (in code/dummy.csv)

      在使用tryCatch()捕获异常,中断程序代码时,需要注意可能造成死循环的情况。(除非你 kill R 程序过程!!!)

    1.  
      # Don't let the user interrupt the code
    2.  
      i <- 1
    3.  
      while(i < 3) {
    4.  
      tryCatch({
    5.  
      Sys.sleep(0.5)
    6.  
      message("Try to escape")
    7.  
      }, interrupt = function(x) {
    8.  
      message("Try again!")
    9.  
      i <<- i + 1
    10.  
      })
    11.  
      }

      tryCatch()还有一个功能模块:finally = { cleanup-code },它指定一个代码块(cleanup-code)(不是函数),无论初始表达是成功还是失败,都运行这段代码块。这对于清理程序(例如,删除文件,关闭连接)非常有用。这个功能等同于使用on.exit(),但它可以被封装在较小的代码块中使用。

  • withCallingHandlers()

      与tryCatch()功能相似的另一种方法是withCallingHandlers()。它们的功能之间主要有两个区别:

    • tryCatch()处理程序的返回值由tryCatch()返回,而withCallingHandlers()的返回值被处理程序忽略。

      1.  
        f <- function() stop("!")
      2.  
        tryCatch(f(), error = function(e) 1)
      3.  
        #> [1] 1
      4.  
        withCallingHandlers(f(), error = function(e) 1)
      5.  
        #> Error in f(): !
    • 通过调用sys.calls()查看相应的中间过程,它的运行相当于traceback()的用法,如下所示,它列出了导致当前函数的所有调用。

      1.  
        f <- function() g()
      2.  
        g <- function() h()
      3.  
        h <- function() stop("!")
      4.  
         
      5.  
        tryCatch(f(), error = function(e) print(sys.calls()))
      6.  
        # [[1]] tryCatch(f(), error = function(e) print(sys.calls()))
      7.  
        # [[2]] tryCatchList(expr, classes, parentenv, handlers)
      8.  
        # [[3]] tryCatchOne(expr, names, parentenv, handlers[[1L]])
      9.  
        # [[4]] value[[3L]](cond)
      10.  
         
      11.  
        withCallingHandlers(f(), error = function(e) print(sys.calls()))
      12.  
        # [[1]] withCallingHandlers(f(),
      13.  
        # error = function(e) print(sys.calls()))
      14.  
        # [[2]] f()
      15.  
        # [[3]] g()
      16.  
        # [[4]] h()
      17.  
        # [[5]] stop("!")
      18.  
        # [[6]] .handleSimpleError(
      19.  
        # function (e) print(sys.calls()), "!", quote(h()))
      20.  
        # [[7]] h(simpleError(msg, call))

      以下为一个示例code

    1.  
      message2error <- function(code) {
    2.  
      withCallingHandlers(code, message = function(e) stop(e))
    3.  
      }
    4.  
       
    5.  
      f <- function() g()
    6.  
      g <- function() message("Hi!")
    7.  
      g()
    8.  
      # Error in message("Hi!"): Hi!
    9.  
      message2error(g())
    10.  
      traceback()
    11.  
      # 10: stop(e) at #2
    12.  
      # 9: (function (e) stop(e))(list(message = "Hi!\n",
    13.  
      # call = message("Hi!")))
    14.  
      # 8: signalCondition(cond)
    15.  
      # 7: doWithOneRestart(return(expr), restart)
    16.  
      # 6: withOneRestart(expr, restarts[[1L]])
    17.  
      # 5: withRestarts()
    18.  
      # 4: message("Hi!") at #1
    19.  
      # 3: g()
    20.  
      # 2: withCallingHandlers(code, message = function(e) stop(e))
    21.  
      # at #2
    22.  
      # 1: message2error(g())

      这些细微的差别很少用到,当你试图捕捉究竟哪里出了问题,并把它传递给另一个函数时除外。在大多数情况下,你不应该需要使用withCallingHandlers()。

综合示例
  1.  
    #!/usr/bin/env Rscript
  2.  
    # tryCatch.r -- experiments with tryCatch
  3.  
     
  4.  
    # Get any arguments
  5.  
    arguments <- commandArgs(trailingOnly=TRUE)
  6.  
    a <- arguments[1]
  7.  
     
  8.  
    # Define a division function that can issue warnings and errors
  9.  
    myDivide <- function(d, a) {
  10.  
    if (a == 'warning') {
  11.  
    return_value <- 'myDivide warning result'
  12.  
    warning("myDivide warning message")
  13.  
    } else if (a == 'error') {
  14.  
    return_value <- 'myDivide error result'
  15.  
    stop("myDivide error message")
  16.  
    } else {
  17.  
    return_value = d / as.numeric(a)
  18.  
    }
  19.  
    return(return_value)
  20.  
    }
  21.  
     
  22.  
    # Evalute the desired series of expressions inside of tryCatch
  23.  
    result <- tryCatch({
  24.  
     
  25.  
    b <- 2
  26.  
    c <- b^2
  27.  
    d <- c+2
  28.  
    if (a == 'suppress-warnings') {
  29.  
    e <- suppressWarnings(myDivide(d,a))
  30.  
    } else {
  31.  
    e <- myDivide(d,a) # 6/a
  32.  
    }
  33.  
    f <- e + 100
  34.  
     
  35.  
    }, warning =