ottenere lo stato di navigazione in una funzione
Ho una funzione come questa:
fun <- function() {
browser()
is_browsing()
}
Vorrei sapere quale is_browsing()
dovrebbe essere il codice di in modo che ritorni TRUE
se la funzione è attualmente in fase di esplorazione, quindi la console sarebbe simile a questa:
> fun()
Called from: fun()
Browse[1]>
debug at #3: is_browsing()
Browse[2]>
TRUE
Tuttavia se commento la browser()
riga, o interrompo la navigazione premendo c
, is_browsing()
dovrebbe tornare FALSE
, in questo modo:
> fun()
Called from: fun()
Browse[1]> c
FALSE
Ho letto debuggingState()
e isdebugged()
ma non sembrano essere di grande aiuto nella mia situazione.
Il vero caso FYI riguarda l'aggiornamento di una trama o di una vista mentre navighiamo, ma solo se stiamo navigando, se non lo siamo, voglio solo tracciare / visualizzare una volta alla fine, per risparmiare risorse.
Risposte
Quando si utilizza il browser, il prompt mostra il livello di navigazione:
Sfoglia [1], Sfoglia [2], ...
> browser()
Called from: top level
Browse[1]> browser()
Called from: top level
Browse[2]>
Questo livello di navigazione è calcolato main.C
da:
browselevel = countContexts(CTXT_BROWSER, 1);
Dov'è CTXT_BROWSER
una costante definita in defn.h
:
CTXT_BROWSER = 16
Puoi utilizzare questa countContexts
funzione interna per ottenere le is_browsing
informazioni che stai cercando:
is_browsing.cpp
#include <Rcpp.h>
#include <R.h>
#include <Rinternals.h>
using namespace Rcpp;
// [[Rcpp::export]]
int is_browsing() {
return Rf_countContexts(16,1);
}
Prova:
library(Rcpp)
sourceCpp('is_browsing.cpp')
test <- function() {
is_browsing()
}
test()
#> [1] 0
browser()
#> Called from: eval(expr, envir, enclos)
test()
#> [1] 1
Creato il 29/08/2020 dal pacchetto reprex (v0.3.0)
Funziona anche se il browser viene chiamato all'interno della funzione:
test2 <- function() {
browser()
is_browsing()
}
test2()
Called from: test2()
Browse[1]> n
debug à #3 :is_browsing()
Browse[2]> n
[1] 1
Se volessi un ritorno VERO / FALSO, il codice Rcpp sarebbe:
#include <Rcpp.h>
#include <R.h>
#include <Rinternals.h>
// [[Rcpp::export]]
Rcpp::LogicalVector is_browsing() {
return Rf_countContexts(16,1) > 0;
}
Partendo dalle idee nel codice di Romain, quindi copiando attraverso la struttura RCNTXT (più un paio di altre strutture che utilizza internamente), sono riuscito a ottenere il codice C ++ di cui restituire il contenuto R_GlobalContext
.
Il codice C ++ ha questo aspetto:
#include <Rcpp.h>
#include <Rinternals.h>
#include <setjmp.h>
extern void* R_GlobalContext ;
typedef struct {int tag, flags; union {int ival; double dval; SEXP sxpval;} u;
} R_bcstack_t;
typedef struct{jmp_buf jmpbuf; int mask_was_saved, saved_mask;} sigjmp_buf[1];
typedef struct RCNTXT {
struct RCNTXT *nextcontext;
int callflag;
sigjmp_buf cjmpbuf;
int cstacktop, evaldepth;
SEXP promargs, callfun, sysparent, call, cloenv, conexit;
void (*cend)(void *);
void *cenddata;
void *vmax;
int intsusp, gcenabled, bcintactive;
SEXP bcbody;
void* bcpc;
SEXP handlerstack, restartstack;
struct RPRSTACK *prstack;
R_bcstack_t *nodestack;
R_bcstack_t *bcprottop;
SEXP srcref;
int browserfinish;
SEXP returnValue;
struct RCNTXT *jumptarget;
int jumpmask;
} RCNTXT, *context;
// [[Rcpp::export]]
Rcpp::List get_RCNTXT(int level){
RCNTXT* res = (RCNTXT*)R_GlobalContext;
if (level > 1) res = res->nextcontext;
return Rcpp::List::create(Rcpp::Named("call_flag") = res->callflag,
Rcpp::Named("c_stack_top") = res->cstacktop,
Rcpp::Named("call_depth") = res->evaldepth,
Rcpp::Named("call_fun") = res->callfun,
Rcpp::Named("sys_parent") = res->sysparent,
Rcpp::Named("call") = res->call,
Rcpp::Named("cloenv") = res->cloenv,
Rcpp::Named("conexit") = res->conexit,
Rcpp::Named("promargs") = res->promargs,
Rcpp::Named("intsusp") = res->intsusp,
Rcpp::Named("gcenabled") = res->gcenabled,
Rcpp::Named("bcintactive") = res->bcintactive,
Rcpp::Named("handlerstack") = res->handlerstack,
Rcpp::Named("restartstack") = res->restartstack,
Rcpp::Named("srcref") = res->srcref,
Rcpp::Named("browserfinish") = res->browserfinish);
}
Ciò ci consente di rivedere i contenuti di R_Globalcontext
:
get_RCNTXT(1)
#> $call_flag #> [1] 12 #> #> $c_stack_top
#> [1] 4
#>
#> $call_depth #> [1] 1 #> #> $call_fun
#> function (level)
#> .Call(<pointer: 0x0000000071282ff0>, level)
#> <bytecode: 0x00000174169448d0>
#>
#> $sys_parent #> <environment: R_GlobalEnv> #> #> $call
#> get_RCNTXT(1)
#>
#> $cloenv #> <environment: 0x0000017416c52a08> #> #> $conexit
#> NULL
#>
#> $promargs #> $promargs[[1]]
#> NULL
#>
#>
#> $intsusp #> [1] 0 #> #> $gcenabled
#> [1] 1
#>
#> $bcintactive #> [1] 0 #> #> $handlerstack
#> NULL
#>
#> $restartstack #> NULL #> #> $srcref
#> NULL
#>
#> $browserfinish
#> [1] 0
Sfortunatamente, il browserfinish
campo restituisce solo uno 0 sia che venga chiamato da browser
o meno. Tuttavia, se la get_RCNTXT
funzione viene chiamata dal browser
prompt, restartstack
mostra che è stata chiamata da browser
. Ciò consente di definire la seguente funzione R una volta che il codice C ++ è stato originato:
is_browser <- function()
{
R <- get_RCNTXT(1)$restartstack
if(is.null(R)) return(FALSE)
class(R[[1]]) == "restart"
}
Ciò consente di interrogare lo stato del browser dal prompt dei comandi:
is_browser()
#> [1] FALSE
> browser()
#> Called from: top level
Browse[1]> is_browser()
#> [1] TRUE
Tuttavia, questo non è così utile come sembra. In primo luogo, ha lo stesso effetto del seguente codice in base R:
is_browser <- function() {
!is.null(findRestart("browser"))
}
In secondo luogo, quando browser
viene chiamato dall'interno di una funzione, il codice che esegue viene valutato nel proprio contesto anziché nel browser
contesto, il che significa is_browser
che restituirà FALSE. Il codice C per browser
, (la funzione effettiva è chiamata do_browser
in main.c) scrive un nuovo contesto che viene rimosso dopo che la funzione è uscita, e questo contesto non è apparentemente puntato da nessun'altra struttura per la durata della funzione, quindi è difficile vedere come is_browser
potrebbe essere scritto per consentire l'accesso a questo contesto.
Sembra quindi che sia necessario scrivere una nuova implementazione di browser
per consentire al contesto esplorato di sapere che è stato esplorato e non vogliamo davvero andare lì.
D'altra parte, il contesto del browser ha pieno accesso al contesto esplorato e poiché il tuo obiettivo finale è quello di consentire l'esecuzione di codice condizionale come i grafici solo in modalità browser, penso che la soluzione migliore sia usare il browser stesso per dire al contesto esplorato in cui si sta esplorando.
Quindi, ad esempio, se fai:
browser_on <- function() {
options(I_am_browsing = TRUE)
}
browser_off <- function() {
options(I_am_browsing = FALSE)
}
is_browser <- function() {
b <- getOption("I_am_browsing")
if(is.null(b)) FALSE else b
}
Ora hai la possibilità durante la navigazione di eseguire in modo condizionale il codice protetto da if(is_browser())
.
Quindi se ti fun
piace questo (con browser()
commentato):
fun <- function() {
#browser()
if(is_browser()) plot(1:10)
if(!is_browser()) "I didn't plot anything"
}
Otterrete:
fun()
#> [1] "I didn't plot anything"
Ma, se esegui fun()
dall'interno di un browser, ottieni:
browser()
Called from: top level
Browse[1]> browser_on()
Browse[1]> fun()

E funziona ancora se browser
viene chiamato all'interno fun
:
fun <- function() {
browser()
if(is_browser()) plot(1:10)
if(!is_browser()) "I didn't plot anything"
}
fun()
#> Called from: fun()
Browse[1]> browser_on()
Browse[1]>
#> debug at #3: if (is_browser()) plot(1:10)
Browse[2]>
#> debug at #3: plot(1:10)
Browse[2]>
#> debug at #4: if (!is_browser()) "I didn't plot anything"
Browse[2]>
Non è una soluzione perfetta perché richiede un comando aggiuntivo durante l'esecuzione nel browser e salva lo stato tramite options
. Dovrai tenerne traccia se chiami browser
più volte dallo stesso ambito. In particolare, devi fare attenzione a chiamare browser_off()
prima di uscire dal browser se stai chiamando browser
dall'ambiente globale.
È descritto nella documentazione per browser , browseText e browseCondition :
Invece di chiamare solo browser (), chiamalo e imposta l'argomento per browseText o browseCondition.
browser(text="foo")
Quindi puoi verificare la condizione per determinare se il browser è in esecuzione:
is_browsing<-function(n)
{
result = FALSE
result = tryCatch({
browserText(n=1)
result = TRUE
}, warning = function(w) {
#warning-handler-code
}, error = function(e) {
# error-handler-code
}, finally = {
#code you always want to execute
})
return (result)
}
Il n = 1 in browseText si riferisce al contesto da cui recuperare il valore.
Se non stai navigando, la chiamata a browseText () genera un errore -> Questo è il motivo per cui lo abbiamo inserito in un tentativo di cattura. Quindi, se viene generato un errore, sappiamo che il browser non è in esecuzione. Se non viene generato alcun errore, il risultato è impostato su true ed è possibile eseguire la propria logica personalizzata.
Per provare, prova:
browser(text="foo")
if(isTRUE(is_browsing())){
print("is browsing!!!")
}else{
print("is not browsing!!!");
}
Quindi commenta la chiamata al browser (text = "foo") e osserva la differenza.
MODIFICA: se non puoi passare un argomento a browser () per qualsiasi motivo, puoi utilizzare il debug invece:
https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/debug
Oppure puoi impostare il valore usando qualche altro debugger esterno.
Questo non è al 100% quello che stai cercando, ma forse hai un'idea di come risolvere il tuo problema? Non ho familiarità con le nozioni di base di C / C ++ R, ma forse puoi sovraccaricare base::browser()
?
Spero che possa aiutare:
list.parent_env <- function() {
ll <- list()
n <- 1
while (!environmentName(.GlobalEnv) %in%
environmentName(parent.frame(n))) {
ll <- c(ll, parent.frame(n))
n <- n + 1
}
return(ll)
}
listofenv2names <- function(env_list) {
names <- unlist(lapply(c(1:length(env_list)), function(i) {
attributes(env_list[[i]])$name
}))
return(names)
}
# https://stackoverflow.com/a/23891089/5784831
mybrowser <- function() {
e <- parent.frame()
attr(e, "name") <- "mybrowser_env"
assign("mybrowser_env", 1,
envir = parent.frame(),
inherits = FALSE, immediate = TRUE)
return(eval(quote(browser()), parent.frame()))
}
is_browsing <- function() {
env_list <- list.parent_env()
r <- "mybrowser_env" %in% listofenv2names(env_list)
print(r)
return(r)
}
subsubfun <- function() {
print("subsubfun")
b <- 2
is_browsing()
return(NULL)
}
subfun <- function() {
print("subfun")
a <- 1
is_browsing()
subsubfun()
return(NULL)
}
fun1 <- function() {
print("fun1")
is_browsing()
mybrowser()
for (i in 1:10) {
is_browsing()
}
is_browsing()
subfun()
return(NULL)
}
fun2 <- function() {
print("fun2")
is_browsing()
return(NULL)
}
fun1()
fun2()
L'output sembra buono:
[1] "fun1"
[1] FALSE
Called from: eval(quote(browser()), parent.frame())
Browse[1]> c
[1] TRUE
[1] "subfun"
[1] TRUE
[1] "subsubfun"
[1] TRUE
[1] "fun2"
[1] FALSE