【问题标题】:knitr inherits variables from a user's environment, even with envir = new.env()knitr 从用户环境继承变量,即使使用 envir = new.env()
【发布时间】:2015-11-22 08:14:15
【问题描述】:

我发现 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() 参数。

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 documentation(它是knit 的包装)明确表示您可以使用envir = new.env()

在编织过程中要评估代码块的环境(可以使用 new.env() 来保证一个空的新环境)。

但是,render 显示与上述相同的行为,原因相同。我对envir = new.env() 的期望(和 rmarkdown 文档)是不正确的,还是我使用不正确?还有其他方法可以保证正在编织的文档中的新环境吗?

【问题讨论】:

  • 你可以使用 baseenv 代替
  • @rawr 如果我使用baseenv,我将无法在 knitr 块中加载任何包。例如,尝试将以上行更改为 writeLines(c("```{r}", "library(ggplot2)", "qplot(rnorm(100))"), "test.Rmd"); knit("test.Rmd", quiet = TRUE, envir = baseenv())

标签: r knitr


【解决方案1】:

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

你可以通过指定parent来避免这种情况:

new.env(parent = baseenv())

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

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

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

为了能够在继承自baseenv() 的干净环境中使用包,您需要手动实现包附加机制,因为 R 包本身不支持环境隔离(grrr!)。或者你使用支持本地附加包的‘box’ package

```{r}
box::use(ggplot2[...])
qplot(rnorm(10))
```

[...] 声明导致包在本地附加(在当前范围内),这与 library 不同。

【讨论】:

  • 我意识到parent = baseenv() 解决方案不允许在 knitr 文档中加载包。例如,如果我们将上面的行更改为writeLines(c("```{r}", "library(ggplot2)", "qplot(rnorm(10))", "```"), "test.Rmd")knit("test.Rmd", quiet = TRUE, envir = new.env(parent = baseenv())),我们会得到一个错误。 (Much more discussion can be found here)。有什么想法可以解决这个问题吗?
  • @DavidRobinson R 包不支持环境隔离(这很糟糕,也是我的“modules” package 的原因之一。但是,可以手动实现该机制(毕竟,这就是我为“模块”做了)并在此处使用它-查看更新的答案。
  • @akhmed 不幸的是,最近有人在 CRAN 上“窃取”了我的包裹名称。 :-/ 名称冲突确实困扰着我的整个 R 体验,无论是在控制台内部还是在 CRAN 上。 ;-) 所以目前我不知道如何在不重命名的情况下将我的包放入 CRAN(我不想这样做,因为我的包已经被其他人使用,重命名它会破坏所有这些代码) .
  • @KonradRudolph,我明白了。一种解决方案可能是同时采用两条路径?比如说,1)在 Github 上保留 modules 以保持兼容性,但也要 2)创建,比如说,一个 rmodules fork 并将其提交给 CRAN?
  • @akhmed 我知道我迟到了四年,但包裹is now on CRAN under the name ‘box’
【解决方案2】:

虽然@Konrad Rudolph's a尝试解决R包系统对global.env的依赖的潜在问题(阻止使用envir=baseenv())非常令人钦佩,但在大多数情况下knit/可能更实用render R markdown 文件在一个单独的进程中通过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 中点击 knit 按钮时,会发生类似的情况。为什么这不是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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多