knitr:创建环境以在完成后删除的块之间共享变量

knitr: Create environment to share variables across chunks which are deleted when finished

我正在开发一个 R 程序包 (https://github.com/rcst/rmaxima),它提供了计算机代数系统 Maxima 的接口。我想包含一个 knitr 引擎,以便它可以直接与 knitr 一起使用。该包具有启动/生成子进程的功能,然后向 Maxima 发送命令并从中获取结果。这样,应该可以在不同的块之间传递结果。

该接口通过创建接口的 Rcpp-class 的新对象来工作。创建对象会产生一个子进程,删除对象会停止该进程。

我希望引擎在每次 knit()ed 文档时启动一个新的子进程,以便结果可重现。我在想我可以创建一个额外的环境来绑定接口对象。引擎检查该对象是否存在于该环境中。如果不存在,则创建,否则引擎可以直接向接口发送代码。当 knit() 退出时,它会退出其环境范围,并且该环境中的所有变量都会自动删除。这样,我不需要停止子进程,因为接口 class 的对象被删除并且进程自动停止。

但我不知道该怎么做。非常感谢任何提示。

yihui 提供了答案here

本质上,(a) 在父框架中设置一个临时变量来检查引擎是否是 运行 并且 (b) 检查块标签列表以确定当前是否是最后一个因此在处理后触发删除和拆除:

 last_label <- function(label = knitr::opts_current$get('label')) {
  if (knitr:::child_mode()) return(FALSE)
  labels <- names(knitr::knit_code$get())
  tail(labels, 1) == label
}

knitr::knit_engines$set(maxima = local({
  mx <- FALSE
  function(options) {
    if (!mx) {
      maxima$startMaxima()
      mx <<- TRUE
    }
    
    ...  # run maxima code
    
    if (last_label(options$label)) {
      maxima$stopMaxima()
      mx <<- FALSE
    }
  }
}))

为了完整起见,我还提出了一个可行但不太稳健的解决方案。

超出范围的对象会在 R 中自动删除。但是,实际删除发生在 R 的垃圾收集期间 gc(),无法直接控制。因此,要在 knit() 完成时删除一个对象,需要在 knit() 的环境中创建该对象,该环境比引擎调用高出一些级别。

原则上可以通过 knit()on.exit() 注册一个进行实际清理的函数,parent.frame(n=...) 可以检索谁的环境。请注意,在注册到 on.exit() 的表达式被调用时,该范围内的所有对象仍然存在。

 maxima.engine <- function(options) { 
    e <- parent.frame(n = sys.parent() - 2)
    if(!exists("mx", envir = e)) {
        message("starting maxima on first chunk")
        assign(x = "mx", value = new(rmaxima:::RMaxima), envir = e)
        assign(x = "execute", value = get("mx", envir = e)$execute, envir = e)
        assign(x = "stopMaxima", value = get("mx", envir = e)$stopMaxima, envir = e)

        # quit maxima "on.exit"ing of knit()
        # eval() doesn't work on "special primitive functions
        # do.call() does ... this may break in the future
        # see https://stat.ethz.ch/pipermail/r-devel/2013-November/067874.html
        do.call("on.exit", list(quote(stopMaxima()), 
                    add = TRUE), envir = e)
    }

    code <- options$code
    out <- character(0);
    for(i in 1:length(code))
        out <- c(out, eval(call("execute", code[i]), envir = e))

    engine_output(options, code, out)
}