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))