我很好奇R是否可以使用它的eval()函数来执行由例如字符串提供的计算。
eval()
这是一个常见的情况:
eval("5+5")
然而,我得到的不是10:
[1] "5+5"
有解决方案吗?
可以使用parse()函数将字符转换为表达式。你需要指定输入为文本,因为parse默认期望一个文件:
parse()
eval(parse(text="5+5"))
eval()函数计算一个表达式,但"5+5"是一个字符串,而不是表达式。使用parse()和text=<string>将字符串更改为表达式:
"5+5"
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
> 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, ......)的能力。
parse(text = ....)
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是相同的:
q5
"call"
e5
"expression"
e5[[1]]
identical(q5, e5[[1]]) # [1] TRUE
现在你也可以使用lazyeval包中的lazy_eval函数。
lazyeval
lazy_eval
> lazyeval::lazy_eval("5+5") [1] 10
类似地使用rlang:
rlang
eval(parse_expr("5+5"))
不知道为什么没有人提到两个Base R函数专门做这件事:str2lang()和str2expression()。它们是parse()的变体,但似乎返回的表达式更简洁:
str2lang()
str2expression()
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()"或更糟糕的文件。
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