Используйте `rmarkdown::render` в ограниченном окружении
У меня есть следующее 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
распознавать списки (первый чанк не потерпел неудачу), но затем игнорирует регулярные назначения в чанках - Почему моя вторая попытка не работает и чем она отличается от моей третьей попытки?
- Это ошибка?
- Какой идиоматический способ сделать это?
1 ответ
Ваши первые два примера терпят неудачу по разным причинам. Чтобы понять обе ошибки, сначала важно немного узнать о том, как куски кода оцениваются knitr и rmarkdown.
Процедура оценки общего кода блока Knitr
Когда вы звоните rmarkdown::render()
в вашем файле каждый фрагмент кода в конечном итоге оценивается вызовом evaluate::evaluate()
, С точки зрения его оценочного поведения и правил определения объема, evaluate()
ведет себя почти так же, как базовая функция R eval()
,
(Куда evaluate::evaluate()
отличается больше всего от eval()
в том, как он обрабатывает вывод каждого вычисленного выражения. Как объяснено в ?evaluate
в дополнение к оценке выражения, переданного в качестве первого аргумента, он "захватывает всю информацию, необходимую для воссоздания вывода, как если бы вы скопировали и вставили код в R-терминал". Эта информация включает в себя графики, предупреждения и сообщения об ошибках, поэтому она так удобна в пакете, как knitr!)
В любом случае, возможный звонок evaluate()
изнутри функции knitr:::block_exec()
выглядит примерно так
evaluate::evaluate(code, envir = env, ...)
в котором:
code
является вектором символьных строк, дающих (возможно, несколько) выражений, составляющих текущий фрагмент.env
это значение, которое вы поставилиenvir
формальный аргумент в вашем первоначальном вызовеrmarkdown::render()
,
Ваш первый пример
В вашем первом примере envir
это список, а не среда. В этом случае оценка выполняется в локальной среде, созданной вызовом функции. Неразрешенные символы (как описано в обоих ?eval
а также ?evaluate
) ищутся первыми в списке прошедших envir
а затем в цепочке сред, начиная с того, что дано enclos
аргумент. В основном, назначения являются локальными для среды временной оценки, которая прекращается после завершения вызова функции.
Так как evaluate()
работает по одному на символьном векторе выражений, когда envir
список, переменные, созданные в одном из этих выражений, не будут доступны для использования в последующих выражениях.
Когда envir
аргумент rmarkdown::render()
это список, ваш блок кода в конечном итоге оценивается с помощью вызова:
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()
возвращает среду, окружающая среда которой является пустой (то есть среда, возвращаемая 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!"