计算以字符串形式给出的表达式

我很好奇R是否可以使用它的eval()函数来执行由例如字符串提供的计算。

这是一个常见的情况:

eval("5+5")

然而,我得到的不是10:

[1] "5+5"

有解决方案吗?

234114 次浏览

可以使用parse()函数将字符转换为表达式。你需要指定输入为文本,因为parse默认期望一个文件:

eval(parse(text="5+5"))

eval()函数计算一个表达式,但"5+5"是一个字符串,而不是表达式。使用parse()text=<string>将字符串更改为表达式:

> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"

调用eval()调用许多行为,其中一些行为不是立即明显的:

> 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

另见tryCatch

或者,你可以使用我的pander包中的evals来捕获输出和所有警告、错误和其他消息以及原始结果:

> 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"
抱歉,但我不明白为什么这么多人甚至认为字符串是可以计算的东西。你必须改变你的心态,真的。 忘掉一边的字符串和另一边的表达式、调用、求值之间的所有连接

(可能)唯一的连接是通过parse(text = ....),所有优秀的R程序员都应该知道,这很少是一种有效或安全的构造表达式(或调用)的方法。而是学习更多关于substitute()quote(),以及可能使用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)

2017年12月:好的,这里有一个例子(在评论中,没有很好的格式):

q5 <- quote(5+5)
str(q5)
# language 5 + 5


e5 <- expression(5+5)
str(e5)
# expression(5 + 5)

如果你有更多的经验,你会了解到q5"call",而e5"expression",甚至e5[[1]]q5是相同的:

identical(q5, e5[[1]])
# [1] TRUE

现在你也可以使用lazyeval包中的lazy_eval函数。

> lazyeval::lazy_eval("5+5")
[1] 10

类似地使用rlang:

eval(parse_expr("5+5"))

不知道为什么没有人提到两个Base R函数专门做这件事:str2lang()str2expression()。它们是parse()的变体,但似乎返回的表达式更简洁:

eval(str2lang("5+5"))


# > 10
  

eval(str2expression("5+5"))


# > 10


也想反驳那些海报上说的任何试图这样做的人都是错误的。我在R中读取作为文本存储在文件中的表达式,并试图计算它们。这些函数非常适合这个用例。

我同意有关于eval和parse的担忧,但如果表达式在字符串中,似乎没有什么可以做的。这个eval解析也被tidyverse专家在glue包中使用,参见https://github.com/tidyverse/glue/blob/d47d6c7701f738f8647b951df418cfd5e6a7bf76/R/transformer.R#L1-L12

如果文本字符串来自一个不可信的来源,也许eval(parse(text="5+5"))的公认答案有安全问题,例如想象user_input = "list.files()"或更糟糕的文件。

下面是一个潜在的解决方案。

其思想是设置表达式要在其中求值的R环境。在R中,大多数“随R而来”的函数实际上都在R启动时自动加载的包中,例如“list”。Files ', 'library'和'attach'函数来自'base'包。通过将计算环境设置为空环境,这些函数将不再对要计算的表达式可用,从而防止恶意代码的执行。在下面的代码中,默认情况下我只包含与算术相关的函数,否则用户可以使用显式允许的函数提供计算环境。

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