knitr 评估公共环境中的所有块(由knit_global() 返回)。这是设计使然;就像源文件中的所有代码都在同一个环境中运行一样,所有块都在一个公共环境中执行。这同样适用于child documents,因为它们(原则上,不是技术上)只是主文档的一部分,外化到另一个文件。
这并不一定会导致意大利面条式代码:没有什么可以阻止用户使用函数和其他对象来组织knitr 文档中的代码/数据。但可能很少有用户这样做……
所以没有对块/子文档的封装机制的原因是它们应该共享一个公共环境,因为它们是一个(主要)的一部分文件。
但是,可以通过一种方式包含子文档,使用户能够控制对象子文档和主文档共享。该解决方案基于与chunk optionchild 非常相似的函数knit_child()。直接调用knit_child()(相对于通过child 选项隐式调用)的优点是可以设置envir 参数,该参数定义“要评估代码块的环境”(来自?knit) .
在knit_child() 附近,我写了包装器IsolatedChild 以简化事情:
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
传递给... 的参数将在子文档中可用。 (命名它们,参见下面的示例。)该函数返回评估子文档的环境。
在list2env 中指定parent 至关重要,我根据this answer 选择了as.environment(2)。否则parent 将默认为parent.frame(),从而将knit_global() 中的对象暴露给子文档。
assign 可用于使从IsolatedChild 返回的对象在全局环境中可用。
注意knit_child 周围的cat(asis_output()) 构造,它确保子文档的输出正确包含在主文档中,而不管当前块中的results 设置如何。
在转到示例之前,最后要说两句:
- 如果子文档和主文档不应该共享 任何 对象,则此方法过于复杂。只需
knit 子文档并使用 \include{} 将其包含在主文档中。
- 这种方法可能存在一些缺陷。特别是“孤立孩子”的封闭环境需要小心,因为搜索路径可能看起来与预期不同。请注意,主文档和子文档共享
knitr 选项。此外,两个文档都可以通过副作用进行交互(options()、par()、打开的设备,...)。
下面是一个完整的例子/演示:
- 块
inputNormal 没有什么特别的,它只是一个正常行为的演示。 inputHidden 演示了IsolatedChild() 的使用,将两个变量传递给子文档。
-
IsolatedChild() 返回这两个值以及第三个对象在子中创建。
-
check 证明传递给/在“孤立子”中创建的对象不会污染全局环境。
-
import 展示了如何使用 assign 将对象从“孤立的孩子”“导入”到全局环境中。
main.Rnw:
\documentclass{article}
\begin{document}
<<setup>>=
library(knitr)
objInMain <- TRUE
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
@
<<inputNormal, child="child_normal.Rnw">>=
@
<<inputHidden, results = "asis">>=
returned <- IsolatedChild(input = "child_hidden.Rnw",
passedValue = 42,
otherPassedValue = 3.14)
cat(sprintf("Returned from hidden child: \\texttt{%s}",
paste(ls(returned), collapse = ", ")))
@
<<check, results = "asis">>=
cat(sprintf("In global evaluation environment: \\texttt{%s}",
paste(ls(), collapse = ", ")))
@
<<import, results = "asis">>=
assign("objInChildHidden", returned$objInChildHidden)
cat(sprintf("In global evaluation environment: \\texttt{%s}",
paste(ls(), collapse = ", ")))
@
\end{document}
child_normal.Rnw:
<<inChildNormal>>=
objInChildNormal <- TRUE # visible in main.Rnw (standard behaviour)
@
child_hidden.Rnw:
Text in \texttt{child\_hidden.Rnw}.
<<inChildHidden>>=
objInChildHidden <- TRUE
print(sprintf("In hidden child: %s",
paste(ls(), collapse = ", ")))
# Returns FALSE.
# Would be TRUE if "parent" weren't specifiet in list2env().
exists("objInMain", inherits = TRUE)
@
main.pdf: