I'm curious to know if R can use its eval()
function to perform calculations provided by e.g. a string.
This is a common ca开发者_如何转开发se:
eval("5+5")
However, instead of 10 I get:
[1] "5+5"
Any solution?
The eval()
function evaluates an expression, but "5+5"
is a string, not an expression. Use parse()
with text=<string>
to change the string into an expression:
> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"
Calling eval()
invokes many behaviours, some are not immediately obvious:
> class(eval(parse(text="5+5")))
[1] "numeric"
> class(eval(parse(text="gray")))
[1] "function"
> class(eval(parse(text="blue")))
Error in eval(expr, envir, enclos) : object 'blue' not found
See also tryCatch.
You can use the parse()
function to convert the characters into an expression. You need to specify that the input is text, because parse expects a file by default:
eval(parse(text="5+5"))
Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side.
The (possibly) only connection is via parse(text = ....)
and all good R programmers should know that this is rarely an efficient or safe means to construct expressions (or calls). Rather learn more about substitute()
, quote()
, and possibly the power of using do.call(substitute, ......)
.
fortunes::fortune("answer is parse")
# If the answer is parse() you should usually rethink the question.
# -- Thomas Lumley
# R-help (February 2005)
Dec.2017: Ok, here is an example (in comments, there's no nice formatting):
q5 <- quote(5+5)
str(q5)
# language 5 + 5
e5 <- expression(5+5)
str(e5)
# expression(5 + 5)
and if you get more experienced you'll learn that q5
is a "call"
whereas e5
is an "expression"
, and even that e5[[1]]
is identical to q5
:
identical(q5, e5[[1]])
# [1] TRUE
Not sure why no one has mentioned two Base R functions specifically to do this: str2lang()
and str2expression()
. These are variants of parse()
, but seem to return the expression more cleanly:
eval(str2lang("5+5"))
# > 10
eval(str2expression("5+5"))
# > 10
Also want to push back against the posters saying that anyone trying to do this is wrong. I'm reading in R expressions stored as text in a file and trying to evaluate them. These functions are perfect for this use case.
Alternatively, you can use evals
from my pander
package to capture output and all warnings, errors and other messages along with the raw results:
> pander::evals("5+5")
[[1]]
$src
[1] "5 + 5"
$result
[1] 10
$output
[1] "[1] 10"
$type
[1] "numeric"
$msg
$msg$messages
NULL
$msg$warnings
NULL
$msg$errors
NULL
$stdout
NULL
attr(,"class")
[1] "evals"
Nowadays you can also use lazy_eval
function from lazyeval
package.
> lazyeval::lazy_eval("5+5")
[1] 10
Similarly using rlang
:
eval(parse_expr("5+5"))
I agree there are concerns around eval and parse, but if the expression is in a string, there appears nothing much that can be done. This eval parse is also used in the glue package by the tidyverse experts, see https://github.com/tidyverse/glue/blob/d47d6c7701f738f8647b951df418cfd5e6a7bf76/R/transformer.R#L1-L12
Perhaps the accepted answer of eval(parse(text="5+5"))
has security concerns if the text string is from an untrusted source, eg imagine user_input = "list.files()"
or worse file.remove...
One potential work around is below.
The idea is to set the R environment in which the expression is to be evaluated. In R, most functions that 'comes with R' are actually in packages that gets autoloaded at R start up, eg 'list.files', 'library' and 'attach' functions come from the 'base' package. By setting the evaluation environment to empty environment, these functions are no longer available to the expression to be evaluated, preventing malicious code from executing. In the code below, by default I include only arithmetic related functions, otherwise user can provide the evaluation environment with explicitly allowed functions.
eval_text_expression <- function(text_expression, data_list, eval_envir = NULL) {
# argument checks
stopifnot(is.character(text_expression) && length(text_expression) == 1)
stopifnot(is.list(data_list))
stopifnot(length(data_list) == 0 || (!is.null(names(data_list)) && all(names(data_list) != "")))
stopifnot(all(!(lapply(data_list, typeof) %in% c('closure', 'builtin'))))
stopifnot(is.null(eval_envir) || is.environment(eval_envir))
# default environment for convenience
if (is.null(eval_envir)) {
arithmetic_funcs <- list("+" = `+`, "-" = `-`, "*" = `*`, "/" = `/`, "^" = `^`, "(" = `(`)
eval_envir = rlang::new_environment(data = arithmetic_funcs, parent = rlang::empty_env())
}
# load data objects into evaluation environment, then evaluate expression
eval_envir <- list2env(data_list, envir = eval_envir)
eval(parse(text = text_expression, keep.source = FALSE), eval_envir)
}
eval_text_expression("(a+b)^c - d", list(a = 1, b = 2, c = 3, d = 4))
# [1] 23
eval_text_expression("list.files()", list())
# Error in list.files() : could not find function "list.files"
eval_text_expression("list.files()", list(), eval_envir = rlang::new_environment(list("list.files" = list.files)))
# succeeds in listing my files if i explicitly allow it
精彩评论