在受限环境中使用 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

所以我对几件事感到困惑,回顾一下:

您的前两个示例因不同原因而失败。要理解这两种失败,首先要了解 knitrrmarkdown.

如何评估代码块。

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!"