我发现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
documentation(knit
的包装)明确表示您可以使用envir = new.env()
:
在编织期间评估代码块的环境(可以使用new.env()来保证空的新环境。)
但是,出于同样的原因,render
显示出与上述相同的行为。我对envir = new.env()
的期望(和rmarkdown文档)是否不正确,或者我是否错误地使用了它?还有另一种方法可以保证文档中的新环境被编织吗?
答案 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文档的代码将在匿名函数的执行环境中执行,这可能导致难以理解的问题(1,2 )。
在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()