在受限环境中使用 rmarkdown::render
Use `rmarkdown::render` in a restricted environment
我有以下 Rmd
我调用的文件 test.Rmd
:
---
title: "test"
output: html_document
---
```{r}
print(y)
```
```{r}
x <- "don't you ignore me!"
print(x)
```
我想按以下方式调用渲染:
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = list(y="hello"))
但失败了:
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
|................................................. | 75%
ordinary text without R code
|.................................................................| 100%
label: unnamed-chunk-2
Quitting from lines 11-13 (test.Rmd)
Error in print(x) : object 'x' not found
第一个块进行得很好,所以有些东西起作用了。如果我在我的全局环境中定义 y
我可以 运行 它没有 envir
参数并且它工作正常。
我想也许 render
不喜欢列表,所以让我们给它一个合适的环境 :
y_env <- as.environment(list(y="hello"))
ls(envir = y_env)
# [1] "y"
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = y_env)
但更糟糕的是,它找不到 print
!
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
Quitting from lines 7-8 (test.Rmd)
Error in eval(expr, envir, enclos) : could not find function "print"
现在文档提到使用函数 new.env
所以出于绝望我尝试了这个:
y_env <- new.env()
y_env$y <- "hello"
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = y_env)
现在可以使用了!
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
|................................................. | 75%
ordinary text without R code
|.................................................................| 100%
label: unnamed-chunk-2
output file: test.knit.md
"C:/Program Files/RStudio/bin/pandoc/pandoc" +RTS -K512m -RTS test.utf8.md --to html --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash --output test.html --smart --email-obfuscation none --self-contained --standalone --section-divs --template "**redacted**\RMARKD~1\rmd\h\DEFAUL~1.HTM" --no-highlight --variable highlightjs=1 --variable "theme:bootstrap" --include-in-header "**redacted**\AppData\Local\Temp\RtmpGm9aXz\rmarkdown-str3f6c5101cb3.html" --mathjax --variable "mathjax-url:https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
Output created: test.html
所以我对几件事感到困惑,回顾一下:
- 为什么
render
识别列表(第一个块没有失败)但随后忽略块中的常规分配
- 为什么我的第二次尝试不起作用,它与我的第三次尝试有何不同?
- 这是一个错误吗?
- 执行此操作的惯用方法是什么?
您的前两个示例因不同原因而失败。要理解这两种失败,首先要了解 knitr 和 rmarkdown.
如何评估代码块。
knitr 的通用代码块评估程序
当您在文件上调用 rmarkdown::render()
时,每个代码块最终都会通过调用 evaluate::evaluate()
来求值。就其评估行为和范围规则而言,evaluate()
的行为几乎与基本 R 函数 eval()
完全一样。
( evaluate::evaluate()
与 eval()
的最大不同在于它如何处理每个计算表达式的输出。如 ?evaluate
中所述,除了计算作为其传递的表达式第一个参数,它 "captures all of the information necessary to recreate the output as if you had copied and pasted the code into an R terminal"。该信息包括图表、警告和错误消息,这就是为什么它在像 knitr 这样的包中如此方便!)
无论如何,最终从函数 knitr:::block_exec()
中调用 evaluate()
看起来像这样
evaluate::evaluate(code, envir = env, ...)
其中:
code
是一个字符串向量,给出了构成当前块的(可能有多个)表达式。
env
是您在最初调用 rmarkdown::render()
时提供的 envir
形式参数的值。
你的第一个例子
在您的第一个示例中,envir
是一个列表,而不是一个环境。在这种情况下,评估将在函数调用创建的本地环境中执行。未解析的符号(在 ?eval
和 ?evaluate
中都有记录)首先在传递给 envir
的列表中查找,然后在以 [=33= 给出的环境开头的环境链中查找] 争论。至关重要的是,赋值对于临时评估环境是本地的,一旦函数调用完成,该环境就会消失。
因为 evaluate()
一次对一个表达式的字符向量进行操作,当 envir
是一个列表时,在其中一个表达式中创建的变量将无法用于后续的表达。
当 rmarkdown::render()
的 envir
参数是一个列表时,您的代码块最终会通过这样的调用进行评估:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- list(y = 1:10)
evaluate(code, envir = env)
## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## Error in print(x): object 'x' not found
效果与使用 eval()
:
完全相同
env <- list(y =1 :10)
eval(quote(x <- "don't you ignore me"), envir = env)
eval(quote(x), envir = env)
## Error in eval(quote(x), envir = env) : object 'x' not found
你的第二个例子
当 envir=
是由 as.environment(list())
返回的环境时,您会因不同的原因而出错。在这种情况下,您的代码块最终会通过这样的调用进行评估:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- as.environment(list(y = 1:10))
evaluate(code, envir = env)
## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## Error in x <- "don't you ignore me!": could not find function "<-"
## > print(x)
## Error in print(x): could not find function "print"
正如您所指出的,这失败了,因为 as.environment()
returns 一个环境,其封闭环境是空环境(即 emptyenv()
返回的环境)。 evaluate()
(就像 eval()
会)在 env
中寻找符号 <-
,当它在那里找不到时,启动封闭环境链,这里, 不包含任何匹配项。 (还记得当 envir
是环境而不是列表时,不使用 enclos
参数。)
推荐的解决方案
要执行您想要的操作,您需要创建一个环境:(1) 包含列表中的所有对象; (2) 将您对 render()
的调用的父环境作为其封闭环境(即通常评估对 render()
的调用的环境)。最简洁的方法是使用漂亮的 list2env()
函数,如下所示:
env <- list2env(list(y="hello"), parent.frame())
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = env)
这样做会导致您的代码块被如下代码评估,这正是您想要的:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- list2env(list(y = 1:10), envir = parent.frame())
evaluate(code, envir = env)
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## [1] "don't you ignore me!"
我有以下 Rmd
我调用的文件 test.Rmd
:
---
title: "test"
output: html_document
---
```{r}
print(y)
```
```{r}
x <- "don't you ignore me!"
print(x)
```
我想按以下方式调用渲染:
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = list(y="hello"))
但失败了:
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
|................................................. | 75%
ordinary text without R code
|.................................................................| 100%
label: unnamed-chunk-2
Quitting from lines 11-13 (test.Rmd)
Error in print(x) : object 'x' not found
第一个块进行得很好,所以有些东西起作用了。如果我在我的全局环境中定义 y
我可以 运行 它没有 envir
参数并且它工作正常。
我想也许 render
不喜欢列表,所以让我们给它一个合适的环境 :
y_env <- as.environment(list(y="hello"))
ls(envir = y_env)
# [1] "y"
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = y_env)
但更糟糕的是,它找不到 print
!
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
Quitting from lines 7-8 (test.Rmd)
Error in eval(expr, envir, enclos) : could not find function "print"
现在文档提到使用函数 new.env
所以出于绝望我尝试了这个:
y_env <- new.env()
y_env$y <- "hello"
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = y_env)
现在可以使用了!
processing file: test.Rmd
|................ | 25%
ordinary text without R code
|................................ | 50%
label: unnamed-chunk-1
|................................................. | 75%
ordinary text without R code
|.................................................................| 100%
label: unnamed-chunk-2
output file: test.knit.md
"C:/Program Files/RStudio/bin/pandoc/pandoc" +RTS -K512m -RTS test.utf8.md --to html --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash --output test.html --smart --email-obfuscation none --self-contained --standalone --section-divs --template "**redacted**\RMARKD~1\rmd\h\DEFAUL~1.HTM" --no-highlight --variable highlightjs=1 --variable "theme:bootstrap" --include-in-header "**redacted**\AppData\Local\Temp\RtmpGm9aXz\rmarkdown-str3f6c5101cb3.html" --mathjax --variable "mathjax-url:https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
Output created: test.html
所以我对几件事感到困惑,回顾一下:
- 为什么
render
识别列表(第一个块没有失败)但随后忽略块中的常规分配 - 为什么我的第二次尝试不起作用,它与我的第三次尝试有何不同?
- 这是一个错误吗?
- 执行此操作的惯用方法是什么?
您的前两个示例因不同原因而失败。要理解这两种失败,首先要了解 knitr 和 rmarkdown.
如何评估代码块。knitr 的通用代码块评估程序
当您在文件上调用 rmarkdown::render()
时,每个代码块最终都会通过调用 evaluate::evaluate()
来求值。就其评估行为和范围规则而言,evaluate()
的行为几乎与基本 R 函数 eval()
完全一样。
( evaluate::evaluate()
与 eval()
的最大不同在于它如何处理每个计算表达式的输出。如 ?evaluate
中所述,除了计算作为其传递的表达式第一个参数,它 "captures all of the information necessary to recreate the output as if you had copied and pasted the code into an R terminal"。该信息包括图表、警告和错误消息,这就是为什么它在像 knitr 这样的包中如此方便!)
无论如何,最终从函数 knitr:::block_exec()
中调用 evaluate()
看起来像这样
evaluate::evaluate(code, envir = env, ...)
其中:
code
是一个字符串向量,给出了构成当前块的(可能有多个)表达式。env
是您在最初调用rmarkdown::render()
时提供的envir
形式参数的值。
你的第一个例子
在您的第一个示例中,envir
是一个列表,而不是一个环境。在这种情况下,评估将在函数调用创建的本地环境中执行。未解析的符号(在 ?eval
和 ?evaluate
中都有记录)首先在传递给 envir
的列表中查找,然后在以 [=33= 给出的环境开头的环境链中查找] 争论。至关重要的是,赋值对于临时评估环境是本地的,一旦函数调用完成,该环境就会消失。
因为 evaluate()
一次对一个表达式的字符向量进行操作,当 envir
是一个列表时,在其中一个表达式中创建的变量将无法用于后续的表达。
当 rmarkdown::render()
的 envir
参数是一个列表时,您的代码块最终会通过这样的调用进行评估:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- list(y = 1:10)
evaluate(code, envir = env)
## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## Error in print(x): object 'x' not found
效果与使用 eval()
:
env <- list(y =1 :10)
eval(quote(x <- "don't you ignore me"), envir = env)
eval(quote(x), envir = env)
## Error in eval(quote(x), envir = env) : object 'x' not found
你的第二个例子
当 envir=
是由 as.environment(list())
返回的环境时,您会因不同的原因而出错。在这种情况下,您的代码块最终会通过这样的调用进行评估:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- as.environment(list(y = 1:10))
evaluate(code, envir = env)
## Or, for prettier printing:
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## Error in x <- "don't you ignore me!": could not find function "<-"
## > print(x)
## Error in print(x): could not find function "print"
正如您所指出的,这失败了,因为 as.environment()
returns 一个环境,其封闭环境是空环境(即 emptyenv()
返回的环境)。 evaluate()
(就像 eval()
会)在 env
中寻找符号 <-
,当它在那里找不到时,启动封闭环境链,这里, 不包含任何匹配项。 (还记得当 envir
是环境而不是列表时,不使用 enclos
参数。)
推荐的解决方案
要执行您想要的操作,您需要创建一个环境:(1) 包含列表中的所有对象; (2) 将您对 render()
的调用的父环境作为其封闭环境(即通常评估对 render()
的调用的环境)。最简洁的方法是使用漂亮的 list2env()
函数,如下所示:
env <- list2env(list(y="hello"), parent.frame())
render('test.Rmd', output_format = "html_document",
output_file = 'test.html',
envir = env)
这样做会导致您的代码块被如下代码评估,这正是您想要的:
library(evaluate)
code <- c('x <- "don\'t you ignore me!"',
'print(x)')
env <- list2env(list(y = 1:10), envir = parent.frame())
evaluate(code, envir = env)
replay(evaluate(code, envir = env))
## > x <- "don't you ignore me!"
## > print(x)
## [1] "don't you ignore me!"