问题1:在使用R语言(RCurl包)抓取网页的过程中,往往会因为有些页面超时,或者页面不存在而导致程序因为异常中断退出,进而使自动批量抓取数据的程序中断,这时就需要有人工干预,重新运行程序或重新启动服务,从而导致维护成本增加。
问题2:使用R语言进行数据处理时,常常需要写批处理程序实现程序自动处理,但是可能会出现一些意想不到的错误,从而导致自动化过程中断,这时需要人工干预,增加不必要的劳动。
我们希望程序运行过程中,如果碰到一些可以预计的错误,可以自动处理它们,忽略这些异常,继续执行后面的代码,那么可以使用try、tryCatch、withCallingHandlers函数进行异常的处理,让程序继续往下执行。
try()
如果表达式运行产生错误提示,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))
Debugging, condition handling, and defensive programming
Using R — Basic error Handing with tryCatch()
R语言-处理异常值或报错的三个示例
如何使用tryCatch处理R语言执行中的异常
R语言使用tryCatch进行简单的错误处理
how-to-skip-an-error-in-a-loop
skip-to-next-value-of-loop-upon-error-in-r-trycatch
How can I check whether a function call results in a warning?
Exception handling in R
tolstoy.newcastle.edu.au
stackoverflow.com/search
github.com/search