2012-11-28 6 views
13

Sono a conoscenza della funzione on.exit in R, che è ottima. Esegue l'espressione quando la funzione di chiamata si chiude, normalmente o come risultato di un errore.Esiste un modo per eseguire un'espressione on.exit() ma solo se completa normalmente, non in caso di errore?

Quello che mi piacerebbe è che l'espressione venga eseguita solo se la funzione di chiamata ritorna normalmente, ma non nel caso di un errore. Ho più punti in cui la funzione potrebbe tornare normalmente e più punti dove potrebbe fallire. C'è un modo per fare questo?

myfunction = function() { 
    ... 
    on.exit(if (just exited normally without error) <something>) 
    ... 
    if (...) then return(point 1) 
    ... 
    if (...) then return(point 2) 
    ... 
    if (...) then return(point 3) 
    ... 
    return (point 4) 
} 

risposta

7

Basta avvolgere gli argomenti di tutte le chiamate di funzione di ritorno con il codice che si desidera eseguire. Quindi, il tuo esempio diventa:

foo = function(thing){do something; return(thing)} 
myfunction = function() { 
    ... 
    if (...) then return(foo(point 1)) 
    ... 
    if (...) then return(foo(point 2)) 
    ... 
    if (...) then return(foo(point 3)) 
    ... 
    return (foo(point 4)) 
} 

o semplicemente fare ogni clausola then in due dichiarazioni. L'uso di on.exit per far leva su alcuni codici in un certo numero di luoghi causerà spaventosi problemi di azione a distanza e farà piangere Dijkstra (leggi il documento "GOTO considerato dannoso" di Dijkstra).

+0

Bel pensiero .. –

+0

+1 Non ci avevo pensato. My 'do something' sta impostando un flag in' .GlobalEnv' (una situazione molto insolita che merita questo approccio, promessa!). Rendendo ogni "allora" due affermazioni sembravano brutte a causa della ripetizione dello stesso codice ogni volta, quindi la rotta "on.exit". Ma avvolgere che "fai qualcosa" in una funzione è pulito! –

+0

euw! più percorsi di uscita, gestori di uscita e ora impostazioni globali! Dijkstra girerà nella sua tomba! – Spacedman

12

Il punto di on.exit() è esattamente da eseguire indipendentemente dallo stato di uscita. Quindi trascura qualsiasi segnale di errore. Questo è afaik equivalente alla dichiarazione finally della funzione tryCatch.

Se si desidera eseguire il codice solo all'uscita normale, è sufficiente inserirlo alla fine del codice. Sì, dovrai ristrutturarlo un po 'usando le istruzioni else e creando solo 1 punto di uscita, ma per alcuni è considerata una buona pratica di codifica.

Usando il tuo esempio, che sarebbe:

myfunction = function() { 
    ... 
    if (...) then out <- point 1 
    ... 
    else if (...) then out <- point 2 
    ... 
    else if (...) then out <- point 3 
    ... 
    else out <- point 4 

    WhateverNeedsToRunBeforeReturning 

    return(out) 
} 

o vedere la answer of Charles per una bella realizzazione di questa idea utilizzando local().

Se ti ostini a usare on.exit(), si può giocare sul funzionamento del meccanismo di traceback a fare qualcosa di simile:

test <- function(x){ 
    x + 12 
}        

myFun <- function(y){ 
    on.exit({ 

     err <- if(exists(".Traceback")){ 
      nt <- length(.Traceback)   
      .Traceback[[nt]] == sys.calls()[[1]] 
     } else {FALSE} 

     if(!err) print("test") 
    }) 
    test(y) 
} 

.Traceback contiene l'ultimo stack di chiamate causando un errore. Devi controllare se la chiamata principale in quella pila è uguale alla chiamata corrente, e in quel caso la tua chiamata molto probabilmente ha gettato l'ultimo errore. Quindi, in base a questa condizione, puoi provare a modificare una soluzione che non utilizzerei mai.

+0

+1 Molte grazie. So cosa intendi e sono pienamente d'accordo. Ma nella particolare situazione che ho, la ristrutturazione del codice sarà difficile. Speravo in un modo per fare la parte 'if (appena uscita normalmente senza errore)'; ad es. esiste un 'last.error' o qualcosa del genere, simile a' .Last.value'? –

+0

@MatthewDowle Esiste, ed è ottenuto da 'geterrmessage()'. Ma se hai eseguito un codice che ha dato un errore prima di eseguire la tua funzione, 'geterrmessage()' restituirà quell'errore. Ecco perché devi controllare lo stack delle chiamate usando 'sys.calls()' e '.Traceback' come nel mio secondo esempio. Fa il 'se (appena uscito normalmente)' chiedi. Non so quanto sia stabile questa soluzione, quindi non ci scommetterei. –

+0

Fantastico, grazie! Non sapevo di ".Traceback" prima. Vedrò che ... –

6

Bit versione più leggibile di mio commento sulla risposta @Joris':

f = function() { 
    ret = local({ 
    myvar = 42 
    if (runif(1) < 0.5) 
     return(2) 
    stop('oh noes') 
    }, environment()) 
    # code to run on success... 
    print(sprintf('myvar is %d', myvar)) 
    ret 
} 
+0

+1 è essenzialmente un altro (bello) modo di fare ciò che ho dichiarato per primo: esegui il codice che vuoi eseguire alla fine della tua funzione. Inoltre, spiega solo cosa fai, quindi le persone non devono scavare nel carico di commenti sotto la mia risposta. –

+0

+1. Potrei semplicemente avvolgere il mio corpo funzionale esistente con quello locale come hai mostrato, allora. Non ci ho pensato neanche io. Grazie! Sperimenterà ... –

Problemi correlati