I\'ve found that a knitr document inherits variables from the user\'s environment, even if the argument envir = new.env()
is provided. How can I prevent it from
While @Konrad Rudolph's attempt to fix the underlying problem (that prevents usage of envir=baseenv()
) of the R package system's reliance on the global.env
is very admirable, it might in most cases be more practical to knit
/render
R markdown files in a separate process through callr using a function like
render_separately <- function(...) callr::r(
function(...) rmarkdown::render(..., envir = globalenv()), args = list(...), show = TRUE)
or
knit_separately <- function(...) callr::r(
function(...) knitr::knit(..., envir = globalenv()), args = list(...), show = TRUE)
. In your example, these (correctly) throw the expected error:
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
#> ```
the envir=globalenv()
is necessary since the code of the Rmarkdown document would otherwise be executed in the execution environment of the anonymous function which can lead to hard to understand problems (1, 2).
When you hit the knit button in Rstudio something similar happens. Why this is not the default/supported in rmarkdown
/knitr
, I don't understand. See theses issues:
(https://github.com/rstudio/rmarkdown/issues/1204
https://github.com/rstudio/rmarkdown/issues/1673 and this question: Difference: "Compile PDF" button in RStudio vs. knit() and knit2pdf()
new.env has a parent argument whose default is parent.frame()
— i.e. the caller. In other words, your new environment inherits all the stuff from your current environment.
You can avoid this by specifying parent
:
new.env(parent = baseenv())
Or, if you want to inherit loaded packages:
new.env(parent = as.environment(2))
And, yes, the render
documentation is somewhat misleading: while new.env()
provides a new, empty environment, it’s not entirely decoupled from the caller, and the caller probably almost never wants to use just new.env()
.
In order to be able to use packages inside a clean environment inherited from baseenv()
, you need to manually implement the package attachment mechanism because R packages do not support environment isolation by themselves (grrr!). Or you use the “modules” package, which supports locally attached packages:
```{r}
modules::import_package('ggplot2', attach = TRUE)
qplot(rnorm(10))
```
The attach = TRUE
argument causes the package to be attached locally, unlike library
.
Here’s a stripped-down version of the “modules” package loading code that could be used as well:
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
}
Usage:
```{r}
library_local('ggplot2')
qplot(rnorm(10))
```