【问题标题】:get browsing state in a function在函数中获取浏览状态
【发布时间】:2020-12-12 14:46:42
【问题描述】:

我有一个这样的功能:

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

我想知道is_browsing()的代码应该是什么,所以如果当前正在浏览该函数,它会返回TRUE,所以控制台看起来像这样:

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

但是,如果我注释掉browser() 行,或者按c 停止浏览,is_browsing() 应该返回FALSE,如下所示:

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

我已经阅读了有关 debuggingState()isdebugged() 的信息,但它们似乎对我的情况没有太大帮助。

真实情况仅供参考是关于在我们浏览时更新绘图或视图,但前提是我们正在浏览,如果我们不是,我只想在最后绘制/查看一次,以节省资源。

【问题讨论】:

  • 我认为如果您可以检查当前的RCNTXT 结构R_GlobalContext(或上下文堆栈中的一些最新条目),您可以获得该信息。我不知道是否有任何用户级函数可以访问它,但如果你能找到评估R_GlobalContext-&gt;browserfinish 的方法,我想它会给你答案。
  • 感谢您,我找到了一些有关如何挖掘 R_GlobalContext 的参考资料,但我仍然需要了解如何处理它们。这是 Romain François 的 C 代码:gist.github.com/romainfrancois/a5c078bbddfbac8533ae。 SO用户eddi的一些Rcpp代码:stackoverflow.com/questions/19686892/…

标签: r debugging


【解决方案1】:

从 Romain 代码中的想法开始,然后复制 RCNTXT 结构(加上它内部使用的几个其他结构),我设法让 C++ 代码返回 R_GlobalContext 的内容。

C++ 代码如下所示:

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

这让我们可以查看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

不幸的是,browserfinish 字段只返回一个 0,无论是否从 browser 调用。但是,如果从browser 提示符调用get_RCNTXT 函数,则restartstack 表明它是从browser 调用的。这允许在获取 C++ 代码后定义以下 R 函数:

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

这允许从命令提示符查询浏览器状态:

is_browser()
#> [1] FALSE

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

但是,这并不像看起来那么有用。首先,它与base R中的以下代码具有相同的效果:

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

其次,当从函数内部调用 browser 时,它运行的代码会在其自己的上下文而不是 browser 上下文中进行评估,这意味着 is_browser 将返回 FALSE。 browser 的 C 代码(实际函数在 main.c 中称为 do_browser)写入了一个新上下文,该上下文在函数退出后被删除,并且在函数,因此很难看出如何编写 is_browser 以允许访问此上下文。

因此,您似乎需要编写browser 的新实现,以允许浏览的上下文知道它正在被浏览,而我们真的不想去那里。

另一方面,浏览器上下文可以完全访问浏览的上下文,并且由于您的最终目标是只在浏览器模式下才允许像绘图这样的条件代码运行,我认为最好的解决方案是使用浏览器本身告诉被浏览的上下文它正在被浏览。

例如,如果你这样做:

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
}

您现在可以在浏览时选择有条件地运行受if(is_browser()) 保护的代码。

如果你有这样的funbrowser() 被注释掉):

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

你会得到:

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

但是,如果你在浏览器中运行fun(),你会得到:

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

如果在fun 内部调用browser,它仍然有效:

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]>

这不是一个完美的解决方案,因为它在浏览器中运行时需要额外的命令,并且它通过options 保存状态。如果您从同一范围内多次调用browser,则需要跟踪这一点。特别是,如果您从全局环境中调用browser,则在退出浏览器之前应小心调用browser_off()

【讨论】:

  • 谢谢 Allan,我很乐意看看你的 C 代码。你的第二个解决方案不会削减它,因为当我调用 browser() 然后通过调用 c 或按下 RStudio 中的播放按钮离开浏览器时,我也需要它。在这种情况下,我会通过浏览器调用,但 is_browing() 将返回 FALSE
  • @Moody_Mudskipper 看到我的更新。这希望能让你更接近一点。我会继续寻找完整的解决方案
  • @Moody_Mudskipper 我想我有一个不同的低技术解决方案现在应该适用于您的用例。
  • 感谢 Allan,但我的主要问题是能够通过调用 c 离开浏览器,并且此时让 is_browser() 更改行为,这在这里不起作用跨度>
  • 如果我们可以破解“c”快捷方式,使其在恢复执行之前产生副作用,我们可能会完成这项工作。我知道在用 eval.parent(quote(browser()) 进行奇异实验时,我曾经在浏览器中意外显示了 c 的定义,但我不记得如果这个“c”可能是一个活动绑定在这一点上有一个副作用,我们可以让它工作。
【解决方案2】:

当您使用浏览器时,提示会显示您的浏览级别:
浏览[1],浏览[2],...

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

此浏览级别是在main.C 中计算的:

browselevel = countContexts(CTXT_BROWSER, 1);

其中CTXT_BROWSERdefn.h 中定义的常量:

CTXT_BROWSER  = 16

您可以使用此内部countContexts 函数来获取您正在寻找的is_browsing 信息:

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

测试:

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

test()
#> [1] 0

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

test()
#> [1] 1

reprex package (v0.3.0) 于 2020 年 8 月 29 日创建

如果在函数中调用浏览器也可以:

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

如果您想要一个 TRUE / FALSE 返回,Rcpp 代码将是:

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

// [[Rcpp::export]]
Rcpp::LogicalVector is_browsing() { 
  return Rf_countContexts(16,1) > 0;
}

【讨论】:

  • 实际上@Waldi,现在已经测试过了,我认为你已经掌握了这个。我很惊讶从函数内部调用时检测到浏览器上下文。这几天让我发疯了——你发现这个给我留下了深刻的印象。为了完整性,我是否可以建议您让您的 Rcpp 代码返回 LogicalVector - 我冒昧地将其附加到您的答案中,但请随时将其删除。
  • 感谢@Allan 的编辑:布尔值准确地回答了is_browsing 的问题,但我将浏览器级别作为输出,因为它可能是有用的额外信息。
  • 这似乎确实有效!我已经在我的包中实现了它,我只遇到了预期的行为。如果在函数上调用 debugonce,它也可以正常工作。非常感谢 Waldi,还有 Allan 的所有努力,我希望我们都能学到新东西。最后我会奖励赏金,以增加曝光率。
  • 祝贺 Waldi 获得丰厚的奖励 - 实至名归。感谢@Moody_Mudskipper 提出的问题——对我来说也是一个很好的学习机会。
  • 我把 RInternals 改成了 Rinternals(没有大写 I),有些系统是区分大小写的,调试起来相当困难!
【解决方案3】:

browserbrowseTextbrowseCondition 的文档中有描述:

不只是调用 browser(),而是调用它并为 browseText 或 browseCondition 设置参数。

browser(text="foo")

然后您可以检查条件以确定浏览器是否正在运行:

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

browseText 中的 n=1 指的是从哪个上下文中检索值。

如果你没有浏览,那么对 browseText() 的调用会抛出一个错误 - > 这就是我们将它包装在 try catch 中的原因。因此,如果抛出错误,我们就知道浏览器没有运行。如果没有抛出错误,则 result 设置为 true,您可以运行自己的自定义逻辑。

要测试,请尝试:

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

然后注释掉对 browser(text="foo") 的调用,看看有什么不同。

编辑: 如果由于任何原因无法将参数传递给 browser(),则可以改用 debug:

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

或者您可以使用其他一些外部调试器设置值。

【讨论】:

  • 我可以重现您的上一个结果,但我无法将其包装到函数中并使用我的代码获得预期的输出(替换 bowser()by browser("foo")
  • @Moody_Mudskipper 你能更详细地解释一下问题是什么吗?
  • 您的意思是您不理解我的问题中的示例吗?或者你想了解更多关于我的现实生活用例的细节吗?
  • @Moody_Mudskipper 我想了解您为什么无法使用我描述的解决方案。您能否详细说明您无法这样做的原因?
  • @Moody_Mudskipper 好的,我现在明白了
【解决方案4】:

这不是 100% 您正在寻找的东西,但也许您知道如何解决您的问题?我不熟悉 C/C++ R-basics,但也许你可以重载base::browser()

我希望这会有所帮助:

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

输出看起来不错:

[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

【讨论】:

  • 感谢 Christoph,但当我调用 f1() 并输入“c”时,它会显示 TRUE,而我已退出浏览器
  • 另外,这样调用浏览器有一个奇怪的问题,我前不久试过了,如果body中有循环,你不能浏览超过1个调用:r.789695.n4.nabble.com/…
  • @Moody_Mudskipper 对于您的第一点:我无法弄清楚控制台和“c”如何与 R 交互。您知道吗?对我来说很清楚,我的方法不能与“c”结合使用,因为我用来检测活动浏览器的环境变量(由mybrowser()设置)没有被“c”删除。
  • “你知道吗?”你可以在这里找到 C 代码:github.com/wch/r-source/blob/…,由 Allan 挖出
  • 我不知道问 SO 是否会做很多事情,我问过 r-devel,因此我的 nabble 链接,但我不确定这是一个错误还是我们的 hack在无证领土上。如果我在一段时间内没有答案,我会再问。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-10
  • 2017-10-16
相关资源
最近更新 更多