16.3 错误处理
有些任务本身就是有风险的。文件或数据库的读取和写入是出了名的容易出错,因为你对文件系统、网络或数据库没有完全的控制权。其实,每当R与其他软件进行交互(如通过rJava
访问Java代码,通过R2WinBUGS
访问WinBUGS,或任何其他数百个R可以连接的软件),都有引发错误的可能。
对于这些危险的任务1,你需要决定出现问题时如何处理。有时,当错误报出时,即使停止执行程序也是没有用的。例如,如果你正在遍历文件并导入它们,如果这时导入失败,你不会希望仅仅停止执行而失去所有已成功导入的数据。
1好吧,连接到一个文件并不那么严重,但它在编程中却属于高风险。
事实上,这点可以概括为:任何时候如果你的循环中正在运行一些有风险的事,你不会想在一次迭代失败后就放弃所有之前的进展。在下例中,我们尝试将列表中的每个元素转换成数据帧:
to_convert <- list(
first = sapply(letters[1:5], charToRaw),
second = polyroot(c(1, 0, 0, 0, 1)),
third = list(x = 1:2, y = 3:5)
)
如果我们只是纯粹运行以上代码而不加任何保护,它会失败:
lapply(to_convert, as.data.frame)
## Error: arguments imply differing number of rows: 2, 3
糟糕!第三个元素因为长度不同而不能转换,而我们也失去了所有进展。
防止彻底失败的最简单的方法就是把容易出错的代码包括在try
函数里:
result <- try(lapply(to_convert, as.data.frame))
现在,虽然错误会被打印到控制台上,但是代码不会停止执行(你可以通过传递silent = TRUE
来抑制这种消息) 。
如果传递给try
函数的代码执行成功(没有抛出任何错误),那么result
就是原来计算的结果,和平时一样。如果代码运行失败,那么result
将是一个try-error
类的对象。这就是说,当你写了一行包括try
的代码,下一行看起来应该是这样:
if(inherits(result, "try-error"))
{
# 用于错误处理的代码
} else
{
#正常执行的代码
}
## NULL
因为你每次都需要包括这些额外的代码,所以使用try
函数的代码看上去有点丑陋。要使之更美观2则可使用tryCatch
。tryCatch
接收一个表达式来安全运行,正如try
一样,且它还有内置的错误处理机制。
2不要低估代码美观的重要性。比起写代码,你会花更多的时间阅读它。
为了处理一个错误,把一个函数传递给一个叫error
的参数。这个error
参数接受一个错误(从技术上讲,它是类simpleError
的对象),你可以任意操作、打印或忽略它。如果这听起来很复杂,请不要担心:它在实际中非常容易。在下例中,当发生错误时,我们会打印错误消息并返回一个空的数据框:
tryCatch(
lapply(to_convert, as.data.frame),
error = function(e)
{
message("An error was thrown: ", e$message)
data.frame()
}
)
## An error was thrown: arguments imply differing number of rows: 2, 3
## data frame with 0 columns and 0 rows
使用tryCatch
的另一个技巧:你可以把一个表达式传递给一个命名为finally
的参数,无论错误是否抛出它都会运行(就像在我们连接数据库时所看到的on.exit
函数一样)。
尽管我们已经尝试了try
和tryCatch
,但是还没有解决问题:在遍历时,即使抛出了一个错误,我们也能保存迭代成功的那部分结果。
为了实现这一目标,需把try
或tryCatch
置于循环内:
lapply(
to_convert,
function(x)
{
tryCatch(
as.data.frame(x),
error = function(e) NULL
)
}
)
## $first
## x
## a 61
## b 62
## c 63
## d 64
## e 65
##
## $second
## x
## 1 0.7071+0.7071i
## 2 -0.7071+0.7071i
## 3 -0.7071-0.7071i
## 4 0.7071-0.7071i
##
## $third
## NULL
因为这是一个公共代码片断,所以plyr
包中有一个函数tryapply
,它正好能处理这种情况,且看上去更精简:
tryapply(to_convert, as.data.frame)
## $first
## x
## a 61
## b 62
## c 63
## d 64
## e 65
##
## $second
## x
## 1 0.7071+0.7071i
## 2 -0.7071+0.7071i
## 3 -0.7071-0.7071i
## 4 0.7071-0.7071i
眼尖的观察者可能会发现,错误处理在这种情况下被直接移除了。