knitr从用户的环境继承变量,即使使用envir = new.env()

时间:2015-08-27 19:31:16

标签: r knitr

我发现knitr文档继承了用户环境中的变量,即使提供了参数envir = new.env()。如何防止它继承这些变量?

例如,假设我使用不存在的变量(y)编写了一个简单的.Rmd文件,编织它并显示生成的文件:

library(knitr)
writeLines(c("```{r}", "y + 1", "```"), "test.Rmd")
knit("test.Rmd", quiet = TRUE, envir = new.env())
# [1] "test.md"
cat(readLines("test.md"), sep = "\n")
# 
# ```r
# y + 1
# #> Error in eval(expr, envir, enclos): object 'y' not found
# ```

当然,我得到的错误是y变量不存在,就像我应该的那样。

但是,如果我在自己的环境中定义y,我发现我现在可以在.Rmd文件中引用y,即使我提供了envir = new.env()参数。 / p>

y <- 3
knit("test.Rmd", quiet = TRUE, envir = new.env())
# [1] "test.md"
cat(readLines("test.md"), sep = "\n")
#
# ```r
# y + 1
# # [1] 4
# ```

我的理解是envir = new.env()应该导致在没有y变量的新环境中评估knitr文档。这是一个问题,因为它允许knitr文档不可重现,引用我未在文档中定义的变量。

请注意,rmarkdown render documentationknit的包装)明确表示您可以使用envir = new.env()

  

在编织期间评估代码块的环境(可以使用new.env()来保证空的新环境。)

但是,出于同样的原因,render显示出与上述相同的行为。我对envir = new.env()的期望(和rmarkdown文档)是否不正确,或者我是否错误地使用了它?还有另一种方法可以保证文档中的新环境被编织吗?

2 个答案:

答案 0 :(得分:9)

new.env has a parent argument,其默认值为parent.frame() - 即来电者。换句话说,您的新环境会继承当前环境中的所有内容。

您可以通过指定parent

来避免这种情况
new.env(parent = baseenv())

或者,如果您想继承加载的包:

new.env(parent = as.environment(2))

而且,是的,render文档有点误导:虽然new.env()提供了一个新的,空的环境,但它并没有完全与调用者分离,调用者可能几乎从不想只使用{ {1}}。

为了能够在继承自new.env()的干净环境中使用包,您需要手动实现包附件机制,因为R包本身不支持环境隔离(grrr!)。或者您使用“modules” package,它支持本地连接的包:

baseenv()

```{r} modules::import_package('ggplot2', attach = TRUE) qplot(rnorm(10)) ``` 参数导致程序包在本地附加,与attach = TRUE不同。

以下是可以使用的“modules” package loading code的精简版本:

library

用法:

require_namespace = function (package) {
    ns = .Internal(getRegisteredNamespace(package))
    if (is.null(ns))
        ns = tryCatch(loadNamespace(package), error = identity)

    ns
}

exhibit_package_namespace = function (namespace, name, parent, export_list) {
    structure(list2env(sapply(export_list, getExportedValue, ns = namespace,
                              simplify = FALSE),
                       parent = parent.env(parent)),
              name = paste('package', name, sep = ':'),
              path = getNamespaceInfo(namespace, 'path'))
}

library_local = function (package, parent = parent.frame()) {
    pkg_ns = require_namespace(package)
    if (inherits(pkg_ns, 'error'))
        stop('Unable to load package ', sQuote(package), '\n',
             'Failed with error: ', sQuote(conditionMessage(pkg_ns)))

    export_list = getNamespaceExports(pkg_ns)
    pkg_env = exhibit_package_namespace(pkg_ns, package, parent, export_list)
    parent.env(parent) = pkg_env
}

答案 1 :(得分:1)

尽管@Konrad Rudolph's a试图解决R软件包系统对envir=baseenv()的依赖的潜在问题(阻止使用global.env),这是非常令人钦佩的,但在大多数情况下,它可能更多对于knit / render R降价文件,通过callr使用类似的功能

render_separately <- function(...) callr::r(
    function(...) rmarkdown::render(..., envir = globalenv()), args = list(...), show = TRUE)

knit_separately <- function(...) callr::r(
    function(...) knitr::knit(..., envir = globalenv()), args = list(...), show = TRUE)

。在您的示例中,这些(正确)引发了预期的错误:

library(knitr)
writeLines(c("```{r}", "y + 1", "```"), "test.Rmd")
y <- 3
knit_separately("test.Rmd", quiet = TRUE)
#> [1] "test.md"
cat(readLines("test.md"), sep = "\n")
#> 
#> ```r
#> y + 1
#> ```
#> 
#> ```
#> ## Error in eval(expr, envir, enclos): object 'y' not found
#> ```

envir=globalenv()是必需的,因为否则Rmarkdown文档的代码将在匿名函数的执行环境中执行,这可能导致难以理解的问题(12 )。

在Rstudio中单击“编织”按钮时,会发生类似的情况。我不明白为什么rmarkdown / knitr中不支持此默认设置。看到这些问题: (https://github.com/rstudio/rmarkdown/issues/1204 https://github.com/rstudio/rmarkdown/issues/1673和以下问题:Difference: "Compile PDF" button in RStudio vs. knit() and knit2pdf()