R語言-程序異常或錯誤處理


R語言異常或錯誤處理


  問題1:在使用R語言(RCurl包)抓取網頁的過程中,往往會因為有些頁面超時,或者頁面不存在而導致程序因為異常中斷退出,進而使自動批量抓取數據的程序中斷,這時就需要有人工干預,重新運行程序或重新啟動服務,從而導致維護成本增加。

  問題2:使用R語言進行數據處理時,常常需要寫批處理程序實現程序自動處理,但是可能會出現一些意想不到的錯誤,從而導致自動化過程中斷,這時需要人工干預,增加不必要的勞動。

  我們希望程序運行過程中,如果碰到一些可以預計的錯誤,可以自動處理它們,忽略這些異常,繼續執行后面的代碼,那么可以使用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 。(示例見如下代碼)

      ###question1:###
      ###求解逆矩陣過程中出錯!!!怎么跳過錯誤!!!###
      set.seed(1)
      count <- 1
      inverses <- vector(mode = "list", 100)
      repeat {
        x <- matrix(sample(0:2, 4, replace = T), 2, 2)
        inverses[[count]] <- solve(x)
        count <- count + 1
        if (count > 100) break
      }
      ################
      
      ###answer1:#####
      count <- 0
      inverses <- vector(mode = "list", 100)  
      repeat {  
          if (count == 100) break  
          count <- count + 1  
          x <- matrix(sample(0:2, 4, replace = T), 2, 2)  
          x.inv <- try(solve(x), silent=TRUE)  
          if ('try-error' %in% class(x.inv)) {
              next
          } else{ 
              inverses[[count]] <- x.inv  
          }
      } 
      inverses
      #inverses[!is.null(inverses)]
      inverses[!(inverses=='NULL')]
      ###############
    

      try() 允許出現錯誤后繼續執行代碼。例如,一般來說,如果你運行的函數引發錯誤,它會立即終止,並且不返回值:

      f1 <- function(x) {
        log(x)
        10
      }
      f1("x")
      #> Error in log(x): non-numeric argument to mathematical function
    

      但是,如果將產生錯誤的語句放在try()中,那么錯誤信息將被打印,但程序會繼續執行:

      f2 <- function(x) {
        try(log(x))
        10
      }
      f2("a")
      #> Error in log(x) : non-numeric argument to mathematical function
      #> [1] 10
    

      我們可以使用try(…, silent=TRUE)函數,隱藏錯誤異常信息。

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

      #默認 silent = FALSE,顯示錯誤信息
      try({
        a <- 1
        b <- "x"
        a + b
      })
    
      #隱藏錯誤信息
      try({
        a <- 1
        b <- "x"
        a + b
      } ,  silent = TRUE)
    

      你可以捕獲try()的輸出,如果程序運行成功,返回計算結果;如果程序運行不成功,則可以通過class()函數返回,錯誤類型 'try-error'。

      success <- try(1 + 2)
      failure <- try("a" + "b")
      class(success)
      #> [1] "numeric"
      class(failure)
      #> [1] "try-error"
      ('try-error' %in% class(success))
      #> [1] FALSE
      ('try-error' %in% class(failure))
      #> [1] TRUE
    

      在list列表中使用try()函數非常有用,可以有效避免個別元素不能計算引起的錯誤。

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

      在R中沒有一個可以識別錯誤類型(class)-"try-error"的函數,我們可以自定義一個函數,然后結合sapply函數,可以非常方便的提取出錯誤類型、錯誤的位置以及錯誤值和正確值。

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

      try()一個非常實用的用法,如下:

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

      下面就是tryCatch()函數的標准語法:

      result = tryCatch({
      	#正常的邏輯
      	expr
      }, warning = function(w) {
      	#出現warning的處理邏輯
      	warning-handler-code
      }, error = function(e) {
      	#出現error的處理邏輯
      	error-handler-code
      }, finally = {
      	#不管出現異常還是正常都會執行的代碼模塊,
      	#一般用來處理清理操作,例如關閉連接資源等。
      	cleanup-code
      }
    

      兩個實際的小例子,code

      #code1:
      get.msg <- function(path)
      {
        con <- file(path, open = "rt", encoding = "latin1")
        text <- readLines(con)
        msg <- tryCatch({
          text[seq(which(text == "")[1] + 1, length(text), 1)]
        }, error = function(e) {
          ""
        })
        close(con)
        return(paste(msg, collapse = "\n"))
      }
      
      #code2:
      library(RMySQL)
      result = tryCatch({
      	#獲取數據連接
      	connect <- dbConnect(MySQL(), dbname="db_olap_web", username="root", password="")
      	#處理其他邏輯
      	#……
      }, warning = function(w) {
      	#這里我只是簡單處理一下
      	#也就是打印到控制台
      	print(w)
      }, error = function(e) {
      	#這里我只是簡單處理一下
      	#也就是打印到控制台
      	print(e)
      }, finally = {
      	#關閉數據庫連接
      	dbDisconnect(connect)
      }
    

      使用tryCatch()函數,根據獲取到的條件信號,返回相應的內置函數處理結果,錯誤、警告、消息等。

      show_condition <- function(code) {
        tryCatch(code,
          error = function(c) "error",
          warning = function(c) "warning",
          message = function(c) "message"
        )
      }
      show_condition(stop("!"))
      #> [1] "error"
      show_condition(warning("?!"))
      #> [1] "warning"
      show_condition(message("?"))
      #> [1] "message"
      
      # If no condition is captured, tryCatch returns the 
      # value of the input
      show_condition(10)
      #> [1] 10
    

      我們可以使用tryCatch()函數來實現的try()函數的功能。需要使用conditionMessage()來提取與原來錯誤相關聯的消息。

      try2 <- function(code, silent = FALSE) {
        tryCatch(code, error = function(c) {
          msg <- conditionMessage(c)
          if (!silent) message(c)
          invisible(structure(msg, class = "try-error"))
        })
      }
      
      try2(1)
      #> [1] 1
      try2(stop("Hi"))
      try2(stop("Hi"), silent = TRUE)
    

      當返回的錯誤值信號有缺省值時,但這是我們希望看到更加細節的錯誤信息,這是就需要我們自己封裝一個tryCatch()函數過程,修改錯誤信息對象,來存儲更多的錯誤信息。下面這個例子是,封裝read.csv()函數的錯誤,將路徑名稱加到錯誤信息中!!!

      read.csv2 <- function(file, ...) {
        tryCatch(read.csv(file, ...), error = function(c) {
          c$message <- paste0(c$message, " (in ", file, ")")
          stop(c)
        })
      }
      read.csv("code/dummy.csv")
      #> Error in file(file, "rt"): cannot open the connection
      read.csv2("code/dummy.csv")
      #> Error in file(file, "rt"): cannot open the connection (in code/dummy.csv)
    

      在使用tryCatch()捕獲異常,中斷程序代碼時,需要注意可能造成死循環的情況。(除非你 kill R 程序過程!!!)

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

      tryCatch()還有一個功能模塊:finally = { cleanup-code },它指定一個代碼塊(cleanup-code)(不是函數),無論初始表達是成功還是失敗,都運行這段代碼塊。這對於清理程序(例如,刪除文件,關閉連接)非常有用。這個功能等同於使用on.exit(),但它可以被封裝在較小的代碼塊中使用。

  • withCallingHandlers()

      與tryCatch()功能相似的另一種方法是withCallingHandlers()。它們的功能之間主要有兩個區別:

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

        f <- function() stop("!")
        tryCatch(f(), error = function(e) 1)
        #> [1] 1
        withCallingHandlers(f(), error = function(e) 1)
        #> Error in f(): !
      
    • 通過調用sys.calls()查看相應的中間過程,它的運行相當於traceback()的用法,如下所示,它列出了導致當前函數的所有調用。

        f <- function() g()
        g <- function() h()
        h <- function() stop("!")
        
        tryCatch(f(), error = function(e) print(sys.calls()))
        # [[1]] tryCatch(f(), error = function(e) print(sys.calls()))
        # [[2]] tryCatchList(expr, classes, parentenv, handlers)
        # [[3]] tryCatchOne(expr, names, parentenv, handlers[[1L]])
        # [[4]] value[[3L]](cond)
        
        withCallingHandlers(f(), error = function(e) print(sys.calls()))
        # [[1]] withCallingHandlers(f(), 
        #    error = function(e) print(sys.calls()))
        # [[2]] f()
        # [[3]] g()
        # [[4]] h()
        # [[5]] stop("!")
        # [[6]] .handleSimpleError(
        #    function (e) print(sys.calls()), "!", quote(h()))
        # [[7]] h(simpleError(msg, call))
      

      以下為一個示例code

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

      這些細微的差別很少用到,當你試圖捕捉究竟哪里出了問題,並把它傳遞給另一個函數時除外。在大多數情況下,你不應該需要使用withCallingHandlers()。

綜合示例
	#!/usr/bin/env Rscript
	# tryCatch.r -- experiments with tryCatch
	
	# Get any arguments
	arguments <- commandArgs(trailingOnly=TRUE)
	a <- arguments[1]
	
	# Define a division function that can issue warnings and errors
	myDivide <- function(d, a) {
	  if (a == 'warning') {
	    return_value <- 'myDivide warning result'
	    warning("myDivide warning message")
	  } else if (a == 'error') {
	    return_value <- 'myDivide error result'
	    stop("myDivide error message")
	  } else {
	    return_value = d / as.numeric(a)
	  }
	  return(return_value)
	}
	
	# Evalute the desired series of expressions inside of tryCatch
	result <- tryCatch({
	  
	  b <- 2
	  c <- b^2
	  d <- c+2
	  if (a == 'suppress-warnings') {
	    e <- suppressWarnings(myDivide(d,a))
	  } else {
	    e <- myDivide(d,a) # 6/a
	  }
	  f <- e + 100
	  
	}, warning = function(war) {
	  
	  # warning handler picks up where error was generated
	  print(paste("MY_WARNING:  ",war))
	  b <- "changing 'b' inside the warning handler has no effect"
	  e <- myDivide(d,0.1) # =60
	  f <- e + 100
	  return(f)
	  
	}, error = function(err) {
	  
	  # warning handler picks up where error was generated
	  print(paste("MY_ERROR:  ",err))
	  b <- "changing 'b' inside the error handler has no effect"
	  e <- myDivide(d,0.01) # =600
	  f <- e + 100
	  return(f)
	  
	}, finally = {
	  
	  print(paste("a =",a))
	  print(paste("b =",b))
	  print(paste("c =",c))
	  print(paste("d =",d))
	  # NOTE:  Finally is evaluated in the context of of the inital
	  # NOTE:  tryCatch block and 'e' will not exist if a warning
	  # NOTE:  or error occurred.
	  #print(paste("e =",e))
	  
	}) # END tryCatch
	
	print(paste("result =",result))

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM