Come posso salvare avvisi ed errori come output di una funzione?

Sto usando lapply per eseguire una funzione complessa su un gran numero di elementi, e mi piacerebbe salvare l’output di ogni object (se presente) insieme a eventuali avvertimenti / errori che sono stati prodotti in modo che io possa sapere quale object è stato prodotto quale avvertimento / errore.

Ho trovato un modo per catturare gli avvisi usando withCallingHandlers ( descritto qui ). Tuttavia, ho bisogno di catturare anche gli errori. Posso farlo avvolgendolo in un tryCatch (come nel codice qui sotto), ma c’è un modo migliore per farlo?

 catchToList <- function(expr) { val <- NULL myWarnings <- NULL wHandler <- function(w) { myWarnings <<- c(myWarnings, w$message) invokeRestart("muffleWarning") } myError <- NULL eHandler <- function(e) { myError <<- e$message NULL } val <- tryCatch(withCallingHandlers(expr, warning = wHandler), error = eHandler) list(value = val, warnings = myWarnings, error=myError) } 

L’output di esempio di questa funzione è:

 > catchToList({warning("warning 1");warning("warning 2");1}) $value [1] 1 $warnings [1] "warning 1" "warning 2" $error NULL > catchToList({warning("my warning");stop("my error")}) $value NULL $warnings [1] "my warning" $error [1] "my error" 

Ci sono diverse domande qui su SO che discutono tryCatch e la gestione degli errori, ma nessuno di quelli che ho trovato rispondono a questo particolare problema. Vedi Come posso verificare se una chiamata di funzione genera un avviso? , warnings () non funziona all’interno di una funzione? Come si può aggirare questo? e Come dire a lapply di ignorare un errore ed elaborare la prossima cosa nella lista? per i più rilevanti.

Forse è lo stesso della tua soluzione, ma ho scritto una factory per convertire semplici vecchie funzioni in funzioni che catturano i loro valori, errori e avvertimenti, quindi posso

 test <- function(i) switch(i, "1"=stop("oops"), "2"={ warning("hmm"); i }, i) res <- lapply(1:3, factory(test)) 

con ogni elemento del risultato contenente il valore, l'errore e / o gli avvertimenti. Ciò funzionerebbe con le funzioni utente, le funzioni di sistema o le funzioni anonime ( factory(function(i) ...) ). Ecco la fabbrica

 factory <- function(fun) function(...) { warn <- err <- NULL res <- withCallingHandlers( tryCatch(fun(...), error=function(e) { err <<- conditionMessage(e) NULL }), warning=function(w) { warn <<- append(warn, conditionMessage(w)) invokeRestart("muffleWarning") }) list(res, warn=warn, err=err) } 

e alcuni aiutanti per gestire la lista dei risultati

 .has <- function(x, what) !sapply(lapply(x, "[[", what), is.null) hasWarning <- function(x) .has(x, "warn") hasError <- function(x) .has(x, "err") isClean <- function(x) !(hasError(x) | hasWarning(x)) value <- function(x) sapply(x, "[[", 1) cleanv <- function(x) sapply(x[isClean(x)], "[[", 1) 

Prova il pacchetto di valutazione .

 library(evaluate) test <- function(i) switch(i, "1"=stop("oops"), "2"={ warning("hmm"); i }, i) t1 <- evaluate("test(1)") t2 <- evaluate("test(2)") t3 <- evaluate("test(3)") 

Al momento non ha un buon modo di valutare l'espressione - questo è principalmente dovuto al fatto che è mirato a riprodurre esattamente quale input di testo fornito da R output alla console.

 replay(t1) replay(t2) replay(t3) 

Cattura anche i messaggi, l'output sulla console e assicura che tutto sia correttamente interfogliato nell'ordine in cui si è verificato.

Ho fuso l’anima di Martins ( https://stackoverflow.com/a/495290/2/2101065 ) e quella della mailing list di R-help che ottieni con la demo(error.catching) .

L’idea principale è di mantenere entrambi, il messaggio di avviso / errore, nonché il comando che triggers questo problema.

 myTryCatch <- function(expr) { warn <- err <- NULL value <- withCallingHandlers( tryCatch(expr, error=function(e) { err <<- e NULL }), warning=function(w) { warn <<- w invokeRestart("muffleWarning") }) list(value=value, warning=warn, error=err) } 

Esempi:

 myTryCatch(log(1)) myTryCatch(log(-1)) myTryCatch(log("a")) 

Produzione:

> myTryCatch (log (1))

$ value [1] 0 $ warning NULL $ error NULL

> myTryCatch (log (-1))

$ value [1] NaN $ warning $ error NULL

> myTryCatch (log ("a"))

$ valore NULL $ avviso NULL $ errore

Lo scopo della mia risposta (e la modifica all’eccellente codice di Martin) è che la funzione di fabbrica restituisce la struttura dei dati prevista se tutto va bene. Se si verifica un avviso, questo viene associato al risultato sotto l’attributo di factory-warning . La funzione setattr di setattr è usata per consentire la compatibilità con quel pacchetto. Se si verifica un errore, il risultato è l’elemento carattere “Si è verificato un errore nella funzione di fabbrica” ​​e l’attributo di factory-error porterà il messaggio di errore.

 #' Catch errors and warnings and store them for subsequent evaluation #' #' Factory modified from a version written by Martin Morgan on Stack Overflow (see below). #' Factory generates a function which is appropriately wrapped by error handlers. #' If there are no errors and no warnings, the result is provided. #' If there are warnings but no errors, the result is provided with a warn attribute set. #' If there are errors, the result retutrns is a list with the elements of warn and err. #' This is a nice way to recover from a problems that may have occurred during loop evaluation or during cluster usage. #' Check the references for additional related functions. #' I have not included the other factory functions included in the original Stack Overflow answer because they did not play well with the return item as an S4 object. #' @export #' @param fun The function to be turned into a factory #' @return The result of the function given to turn into a factory. If this function was in error "An error as occurred" as a character element. factory-error and factory-warning attributes may also be set as appropriate. #' @references #' \url{http://stackoverflow.com/questions/4948361/how-do-i-save-warnings-and-errors-as-output-from-a-function} #' @author Martin Morgan; Modified by Russell S. Pierce #' @examples #' f.log <- factory(log) #' f.log("a") #' f.as.numeric <- factory(as.numeric) #' f.as.numeric(c("a","b",1)) factory <- function (fun) { errorOccurred <- FALSE library(data.table) function(...) { warn <- err <- NULL res <- withCallingHandlers(tryCatch(fun(...), error = function(e) { err <<- conditionMessage(e) errorOccurred <<- TRUE NULL }), warning = function(w) { warn <<- append(warn, conditionMessage(w)) invokeRestart("muffleWarning") }) if (errorOccurred) { res <- "An error occurred in the factory function" } if (is.character(warn)) { data.table::setattr(res,"factory-warning",warn) } else { data.table::setattr(res,"factory-warning",NULL) } if (is.character(err)) { data.table::setattr(res,"factory-error",err) } else { data.table::setattr(res, "factory-error", NULL) } return(res) } } 

Poiché non inseriamo il risultato in un elenco aggiuntivo, non possiamo fare il tipo di ipotesi che consentono alcune delle sue funzioni di accesso, ma possiamo scrivere semplici controlli e decidere come gestire i casi come appropriato per il nostro particolare risultato struttura dati.

 .has <- function(x, what) { !is.null(attr(x,what)) } hasWarning <- function(x) .has(x, "factory-warning") hasError <- function(x) .has(x, "factory-error") isClean <- function(x) !(hasError(x) | hasWarning(x))