eval(parse())、eval(str2lang())、eval(str2expression()) 和 eval(call()[[1]]) 之间有什么区别?

Noa*_*oah 5 eval r

df我正在编写一个函数来从给定的 data.frame或环境中提取作为字符串提供的变量env。最初,我一直使用该eval(parse(text=s), df, env)结构来做到这一点,但我了解到还有更有效的替代方案。其他选项包括:

  1. eval(str2lang(s), df, env)
  2. eval(str2expression(s), df, env)
  3. eval(call(s)[[1]], df, env)

也可能有一个get解决方案,但我不知道它是否可以先检查变量是否在其中,df然后再检查env变量是否不在。

使用microbenchmark,似乎call是最快的:

library(microbenchmark)

x1 = 1
df = data.frame(x2 = 2)

microbenchmark(call = eval(call('x1')[[1]], df), 
               parse = eval(parse(text='x1'), df), 
               str2lang = eval(str2lang('x1'), df), 
               str2exp = eval(str2expression('x1'), df), 
               check = "identical")
#> Unit: microseconds
#>      expr    min      lq     mean  median      uq     max neval cld
#>      call  1.128  1.2115  1.60815  1.4585  1.6360   4.659   100  a 
#>     parse 39.183 39.8705 46.60755 40.2405 42.0415 135.462   100   b
#>  str2lang  2.235  2.3570  3.26144  2.5995  2.8925  24.641   100  a 
#>   str2exp  2.230  2.3200  2.81387  2.4780  2.6970  10.312   100  a

microbenchmark(call = eval(call('x2')[[1]], df), 
               parse = eval(parse(text='x2'), df), 
               str2lang = eval(str2lang('x2'), df), 
               str2exp = eval(str2expression('x2'), df), 
               check = "identical")
#> Unit: microseconds
#>      expr    min     lq     mean  median      uq     max neval cld
#>      call  1.124  1.194  1.47770  1.3675  1.5795   9.031   100  a 
#>     parse 38.254 38.762 40.21497 38.9630 39.3120 116.510   100   b
#>  str2lang  2.214  2.304  2.55036  2.3960  2.6530  10.639   100  a 
#>   str2exp  2.238  2.331  2.50011  2.4210  2.6515   3.619   100  a
Run Code Online (Sandbox Code Playgroud)

reprex 包(v0.3.0)创建于 2020-04-23

因此,我倾向于使用call,但我想确保这样做不会产生任何意想不到的后果,而不是使用其他解决方案。换句话说,在什么情况下(在我使用它们的上下文中)这四种方法不会给出相同的答案,导致人们倾向于其中一种而不是其他?

Don*_*nen 4

\n

callas.symbol很好,我认为as.symbol更好。as.symbol是明确的 R 术语(as.name是 S 术语)并且与 一样快call。调用可以包含任何 R 对象,包括符号和其他调用,而且文档无处不在。由于字符串可能包含标点符号,我们无法确定在哪一点失败str2langstr2expression可能弹出什么错误消息。

\n

检查环境中是否存在变量比检查 data.frames 更安全。

\n

差异

\n

深入了解一下它们的主要区别在于它们的类别:

\n
l = list(w = as.symbol("x"), x = str2lang("x"), y = str2expression("x"), z = call("x"))\nrbind(sapply(l, class), sapply(l, typeof), sapply(l, is.language))\n     w        x        y            z         \n[1,] "name"   "name"   "expression" "call"    \n[2,] "symbol" "symbol" "expression" "language"\n[3,] "TRUE"   "TRUE"   "TRUE"       "TRUE"  \n
Run Code Online (Sandbox Code Playgroud)\n

一个很好的起点是R 语言定义,例如 Hadley Wickham 的Advanced R源代码)中的表达式一章,然后阅读?call, ?parse, rlang::call2

\n

这些是什么?

\n

评估 R 语言的设计源代码)中,给出了以下定义:

\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\ xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\ xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0调用参数为可以用符号命名的表达式姓名

\n
    \n
  • 表达——一个多个动作
  • \n
  • 调用——表示调用函数的动作
  • \n
  • 符号(名称) -R 对象 ( 2.1.3 )
  • \n
\n

什么时候使用哪一个?

\n

?call文档中:

\n
\n

相反as.call(<string>),请考虑使用str2lang(*)which 是 的高效版本parse(text=*)call()as.call(),比parse()基于方法更可取。

\n
\n

使用pryr::show_c_source(.Internal(str2lang(s)))我们可以看到str2langstr2expression两者都调用C 函数 do_str2lang,但参数不同。这种差异可以在以下位置找到?parse

\n
    \n
  • str2expression, 等于parse(text = "x", keep.source = F)始终是一个表达式。
  • \n
  • str2lang("x")、等于parse(text = "x", keep.source = F)[[1]]可以计算为调用符号、NULL数字、整数或逻辑,即调用或更简单的
  • \n
\n

R 语言定义中提到,表达式仅在传递给 时才进行求值eval,其他语言对象可能在某些意外情况下进行求值。这些案例是什么,我找不到。

\n

我们可以在所有方法中抛出一些边缘情况,看看哪些方法可能会失败:

\n
    \n
  • "\\""读取数据时错误解析引用?
  • \n
  • " "空变量?变量名中有空格?
  • \n
  • "_"非法令牌(遗留赋值运算符)问题?
  • \n
  • 反引号
  • \n
  • 不适用
  • \n
\n
# if it breaks, show how it breaks\ndo <- function(x, ...) tryCatch(eval(x, ..1, ..2), error = function(t) t$message)\ncheck <- function(x, ...){\n  list(\n    do(call(x)[[1]], ...),\n    do(as.symbol(x), ...),\n    do(str2lang(x), ...),\n    do(str2expression(x), ...)\n  )\n}\n\n# test 1: some variables do not exist\ne <- new.env() ; e$x1 <- 5\ndf = data.frame(x2 = 3)\nno_var <- lapply(list("x1", "x2", "\\"", " ", "_", "`", NA_character_), check, df, e)\n\n# test 2: some variables exist\ne <- new.env() ; e$x1 = 5; e$`_` = 2 ; e$"\\"" = 5; e$`NA` <- 5\ndf = data.frame(x2 = 3, " " = 7, "_" = 2)\nvar <- lapply(list("x1", "x2", "\\"", " ", "_", "`", NA_character_), check, df, e)\n\n# difference in outcomes in\nvar[3:7]\nno_var[3:7]\n
Run Code Online (Sandbox Code Playgroud)\n

当变量不存在时:\nas.symbol并且call始终到达evalstr2lang并且str2expression在所有情况下都会提前失败。与给出空字符串时str2expression不同。str2lang

\n

当变量存在时:\nas.symbolcall在情况 3、4、7 中成功,同时str2lang抛出str2expression错误。

\n

顺便说一句,在 data.frames 中存储变量时要小心,它们很容易损坏。

\n
names(data.frame(" " = 1, "_" = 2, "\\"" = 3, "`" = 4))\n[1] "X."   "X_"   "X..1" "X..2"\n
Run Code Online (Sandbox Code Playgroud)\n