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

发布于 2024-08-10 23:48:32 字数 192 浏览 11 评论 0原文

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

这是一个常见的情况:

eval("5+5")

但是,我得到的不是 10,而是:

[1] "5+5"

有解决方案吗?

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 case:

eval("5+5")

However, instead of 10 I get:

[1] "5+5"

Any solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

合约呢 2024-08-17 23:48:32

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

> 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

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.

亚希 2024-08-17 23:48:32

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

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

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"))
安静 2024-08-17 23:48:32

抱歉,但我不明白为什么太多人甚至认为字符串是可以评估的东西。你必须改变你的心态,真的。
忘记一侧的字符串与另一侧的表达式、调用、评估之间的所有连接。

(可能)唯一的连接是通过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 是一个 “表达式”,甚至 e5[[1]]q5 相同:

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

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
云巢 2024-08-17 23:48:32

不知道为什么没有人专门提到两个 Base R 函数来执行此操作:str2lang() 和 str2expression()。这些是 parse() 的变体,但似乎更干净地返回表达式:

eval(str2lang("5+5"))

# > 10
  
eval(str2expression("5+5"))

# > 10

还想反驳海报说任何试图这样做的人都是错误的。我正在读取作为文本存储在文件中的 R 表达式,并尝试对它们求值。这些功能非常适合此用例。

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.

千秋岁 2024-08-17 23:48:32

现在,您还可以使用 lazyeval 包中的 lazy_eval 函数。

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

Nowadays you can also use lazy_eval function from lazyeval package.

> lazyeval::lazy_eval("5+5")
[1] 10
隔纱相望 2024-08-17 23:48:32

或者,您可以使用我的 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"

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"
他是夢罘是命 2024-08-17 23:48:32

类似地使用 rlang:

eval(parse_expr("5+5"))

Similarly using rlang:

eval(parse_expr("5+5"))
故人的歌 2024-08-17 23:48:32

我同意 eval 和 parse 存在一些问题,但如果表达式位于字符串中,则似乎无能为力。 tidyverse 专家也在glue 包中使用了此 eval 解析,请参阅 https://github.com/tidyverse/glue/blob/d47d6c7701f738f8647b951df418cfd5e6a7bf76/R/transformer.R#L1-L12

也许是eval(parse(text="5+ 5")) 如果文本字符串来自不受信任的来源,则存在安全问题,例如想象 user_input = "list.files()" 或更糟的 file.remove...

一项潜在的工作周围是下面。

这个想法是设置要在其中计算表达式的 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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文