Browserstatus in einer Funktion abrufen

Aug 24 2020

Ich habe eine Funktion wie diese:

fun <- function() {
  browser()
  is_browsing()
} 

Ich würde gerne wissen, wie der Code is_browsing()lauten soll, damit er zurückgegeben wird, TRUEwenn die Funktion gerade durchsucht wird. Die Konsole würde also folgendermaßen aussehen:

> fun()
Called from: fun()
Browse[1]> 
debug at #3: is_browsing()
Browse[2]> 
TRUE

Wenn ich jedoch die browser()Zeile auskommentiere oder das Surfen durch Drücken von stoppe c, is_browsing()sollte FALSEdies wie folgt zurückkehren :

> fun()
Called from: fun()
Browse[1]> c
FALSE

Ich habe darüber gelesen debuggingState()und isdebugged()aber sie scheinen in meiner Situation nicht viel zu helfen.

Im wirklichen Fall geht es zu Ihrer Information darum, ein Diagramm oder eine Ansicht beim Durchsuchen zu aktualisieren, aber nur, wenn wir surfen, wenn nicht, möchte ich am Ende nur einmal zeichnen / anzeigen, um Ressourcen zu sparen.

Antworten

4 Waldi Aug 29 2020 at 13:03

Wenn Sie den Browser verwenden, zeigt die Eingabeaufforderung die Durchsuchungsstufe an:
Durchsuchen [1], Durchsuchen [2], ...

> browser()
Called from: top level 
Browse[1]> browser()
Called from: top level 
Browse[2]> 

Diese Durchsuchungsstufe wird berechnet main.Cdurch:

browselevel = countContexts(CTXT_BROWSER, 1);

Wo CTXT_BROWSERist eine Konstante definiert in defn.h:

CTXT_BROWSER  = 16

Mit dieser internen countContextsFunktion is_browsingkönnen Sie die gesuchten Informationen abrufen:

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);
}

Prüfung :

library(Rcpp)
sourceCpp('is_browsing.cpp')
test <- function() {
  is_browsing()
}

test()
#> [1] 0

browser()
#> Called from: eval(expr, envir, enclos)

test()
#> [1] 1

Erstellt am 2020-08-29 durch das reprex-Paket (v0.3.0)

Funktioniert auch, wenn der Browser innerhalb der Funktion aufgerufen wird:

test2 <- function() {
  browser()
   is_browsing()
 }
test2()
Called from: test2()
Browse[1]> n
debug à #3 :is_browsing()
Browse[2]> n
[1] 1

Wenn Sie eine TRUE / FALSE-Rückgabe wünschen, lautet der Rcpp-Code:

#include <Rcpp.h>
#include <R.h>
#include <Rinternals.h>

// [[Rcpp::export]]
Rcpp::LogicalVector is_browsing() { 
  return Rf_countContexts(16,1) > 0;
}
7 AllanCameron Aug 26 2020 at 23:44

Angefangen mit den Ideen in Romains Code bis hin zum Kopieren über die RCNTXT-Struktur (plus einige andere Strukturen, die intern verwendet werden), gelang es mir, den C ++ - Code dazu zu bringen, den Inhalt von zurückzugeben R_GlobalContext.

Der C ++ - Code sieht folgendermaßen aus:

#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);
}

Dadurch können wir den Inhalt von 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

Leider gibt das browserfinishFeld nur eine 0 zurück, unabhängig davon, ob es von aufgerufen wurde browseroder nicht. Wenn die get_RCNTXTFunktion jedoch über die browserEingabeaufforderung aufgerufen wird, restartstackzeigt das an , dass sie von aufgerufen wurde browser. Auf diese Weise kann die folgende R-Funktion definiert werden, sobald der C ++ - Code bezogen wurde:

is_browser <- function()
{
  R <- get_RCNTXT(1)$restartstack
  if(is.null(R)) return(FALSE)
  class(R[[1]]) == "restart"
}

Dadurch kann der Browser-Status über die Eingabeaufforderung abgefragt werden:

is_browser()
#> [1] FALSE

> browser()
#> Called from: top level 
Browse[1]> is_browser()
#> [1] TRUE

Dies ist jedoch nicht so nützlich, wie es scheint. Erstens hat es den gleichen Effekt wie der folgende Code in Basis R:

is_browser <- function() {
  !is.null(findRestart("browser"))
}

Zweitens wird beim browserAufrufen innerhalb einer Funktion der von ihr ausgeführte Code in einem eigenen Kontext und nicht im browserKontext ausgewertet , was bedeutet is_browser, dass FALSE zurückgegeben wird. Der C-Code für browser(die eigentliche Funktion wird do_browserin main.c aufgerufen) schreibt einen neuen Kontext, der nach dem Beenden der Funktion entfernt wird, und auf diesen Kontext wird anscheinend für die Dauer der Funktion von keiner anderen Struktur verwiesen schwer zu sehen, wie is_browsergeschrieben werden könnte, um den Zugang zu diesem Kontext zu ermöglichen.

Es scheint daher, dass Sie eine neue Implementierung von schreiben müssten browser, damit der durchsuchte Kontext weiß, dass er durchsucht wurde, und wir möchten wirklich nicht dorthin gehen.

Auf der anderen Seite hat der Browserkontext vollen Zugriff auf den durchsuchten Kontext, und da Ihr Endziel darin besteht, die Ausführung von Plots mit bedingtem Code nur im Browsermodus zuzulassen, ist es meiner Meinung nach die beste Lösung, den Browser selbst zu verwenden, um das zu erkennen durchsuchter Kontext, in dem es durchsucht wird.

Wenn Sie zum Beispiel Folgendes tun:

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
}

Sie haben jetzt die Möglichkeit, beim Surfen unter bestimmten Bedingungen Code auszuführen, der durch geschützt ist if(is_browser()).

Dann, wenn Sie dies funmögen (mit browser()auskommentiert):

fun <- function() {
  #browser()
  if(is_browser()) plot(1:10)
  if(!is_browser()) "I didn't plot anything"
}

Sie erhalten:

fun()
#> [1] "I didn't plot anything"

Wenn Sie jedoch fun()in einem Browser ausgeführt werden, erhalten Sie:

browser()
Called from: top level 
Browse[1]> browser_on()
Browse[1]> fun()

Und es funktioniert immer noch, wenn browseres im Inneren aufgerufen wird 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]>

Es ist keine perfekte Lösung, da für die Ausführung im Browser ein zusätzlicher Befehl erforderlich ist und der Status über gespeichert wird options. Sie müssen dies nachverfolgen, wenn Sie browsermehrmals aus demselben Bereich anrufen . Insbesondere sollten Sie browser_off()vor dem Beenden des Browsers vorsichtig anrufen, wenn Sie browseraus der globalen Umgebung anrufen .

1 RahulIyer Aug 26 2020 at 20:19

Es wird in der Dokumentation für Browser , browseText und browseCondition beschrieben :

Anstatt nur browser () aufzurufen, rufen Sie es auf und legen Sie das Argument für browseText oder browseCondition fest.

browser(text="foo")

Anschließend können Sie nach der Bedingung suchen, um festzustellen, ob der Browser ausgeführt wird:

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)
}

Das n = 1 in browseText bezieht sich auf den Kontext, aus dem der Wert abgerufen werden soll.

Wenn Sie nicht surfen, wird beim Aufruf von browseText () ein Fehler ausgegeben -> Aus diesem Grund haben wir ihn in einen Try-Catch eingeschlossen. Wenn also ein Fehler ausgegeben wird, wissen wir, dass der Browser nicht ausgeführt wird. Wenn kein Fehler ausgegeben wird, wird das Ergebnis auf true gesetzt und Sie können Ihre eigene benutzerdefinierte Logik ausführen.

Versuchen Sie zum Testen:

browser(text="foo")
if(isTRUE(is_browsing())){
    print("is browsing!!!")
}else{
    print("is not browsing!!!");
}

Kommentieren Sie dann den Aufruf des Browsers aus (text = "foo") und sehen Sie den Unterschied.

BEARBEITEN: Wenn Sie aus irgendeinem Grund kein Argument an browser () übergeben können, können Sie stattdessen debug verwenden:

https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/debug

Oder Sie können den Wert mit einem anderen externen Debugger festlegen.

Christoph Aug 28 2020 at 22:38

Dies ist nicht 100% das, wonach Sie suchen, aber vielleicht bekommen Sie eine Idee, wie Sie Ihr Problem lösen können? Ich bin nicht mit C / C ++ R-Grundlagen vertraut, aber vielleicht können Sie eine Art Überlastung base::browser()?

Ich hoffe das hilft:

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()

Ausgabe sieht gut aus:

[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