2015-08-27 19 views
12

我發現knitr文檔會繼承用戶環境中的變量,即使提供了參數envir = new.env()也是如此。我怎樣才能防止它繼承這些變量?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,我覺得我現在可以參考y在.Rmd文件,即使我給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 renderdocumentation(這大約是knit包裝)明確說,你可以使用envir = new.env()

其中的代碼塊是編織過程中進行評估(可使用new.env環境()來保證一個空的新環境)。

但是,出於同樣的原因,render顯示與上述相同的行爲。我的期望(和rmarkdown文檔)是否對於envir = new.env()錯誤,或者我使用不正確?還有另外一種方法可以保證正在編織文件的新環境?

+0

如果我使用'baseenv'你可以使用baseenv代替 – rawr

+0

@rawr,我無法在knitr塊中加載任何軟件包。例如,嘗試將上面的行更改爲'writeLines(c(「'''{r}」,「library(ggplot2)」,「qplot(rnorm(100))」),「test.Rmd」); knit(「test.Rmd」,quiet = TRUE,envir = baseenv())' –

回答

9

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!)。或者你使用「modules」 package,它支持本地連接的包:

```{r} 
modules::import_package('ggplot2', attach = TRUE) 
qplot(rnorm(10)) 
``` 

attach = TRUE參數會導致包在本地連接,不像library

這裏的「modules」 package loading code的精簡版本,可以被使用:

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 
} 

用法:

```{r} 
library_local('ggplot2') 
qplot(rnorm(10)) 
``` 
+0

我意識到'parent = baseenv()'解決方案不允許將包加載到knitr文檔中。例如,如果我們將上面的行改爲'writeLines(c(「'''{r}」,「library(ggplot2)」,「qplot(rnorm(10))」,「'''」),「test .Rmd「)和'knit(」test.Rmd「,quiet = TRUE,envir = new.env(parent = baseenv()))'我們得到一個錯誤。 ([更多討論可以在這裏找到](https://github.com/jennybc/reprex/issues/11))。任何想法我們如何解決這個問題? –

+1

@DavidRobinson R包不支持環境隔離(這是糟糕的,也是我的[「模塊」包](https://github.com/klmr/modules)的原因之一。但是,可以手動實現這個機制(畢竟,這就是我爲「模塊」所做的),並在這裏使用它 - 查看更新後的答案 –

+0

@KonradRudolph這真是太棒了,謝謝!這是一個非常基礎的增強,這個軟件包需要把它變成CRAN。可能的增強:在'library_local'中添加'package = as.character(替代(package))'作爲第一行,然後'library_local(ggplot2)'和'library_local(「ggplot2」)'都會工作 – akhmed