R中另一个函数的非标准评估

qed*_*qed 19 r

以下是哈德利高级R书中的一个例子:

sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))

subset2 <- function(x, condition) {
  condition_call <- substitute(condition)
  r <- eval(condition_call, x, parent.frame())
  x[r, ]
}

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset2(x, condition))
}

subscramble(sample_df, a >= 4)
# Error in eval(expr, envir, enclos) : object 'a' not found
Run Code Online (Sandbox Code Playgroud)

哈德利解释说:

你能看出问题所在吗?condition_call包含表达式条件.因此,当我们评估condition_call时,它还会计算条件,其值为a> = 4.但是,由于在父环境中没有名为a的对象,因此无法计算.

据我所知,a在父env中没有,但是,eval(condition_call, x, parent.frame())在x中包含了条件调用(包含用作环境的data.frame)parent.frame().只要有一个以ax 命名的列,为什么会有任何问题?

Jos*_*ien 15

TL;博士

subset2()从内调用subscramble(), condition_call的值是符号condition(而不是呼叫a >= 4时,它会被直接调用该结果).首先subset()调用eval()搜索(data.frame ).没有找到它在那里,在它未来的搜索它确实找到名为对象 .conditionenvir=xsample_dfenclos=parent.frame()condition

该对象是一个promise对象,其表达式槽位 a >= 4于其评估环境中.GlobalEnv.除非在搜索路径中a找到一个名为的对象,否则.GlobalEnv对promise的评估将失败,观察到的消息为:Error in eval(expr, envir, enclos) : object 'a' not found.


详细解释

发现这里出错的一个好方法是browser()subset2() 失败的行之前插入一个 调用.这样,我们可以直接和间接地(从另一个函数中)调用它,并检查它在第一种情况下成功的原因并在第二种情况下失败.

subset2 <- function(x, condition) {
  condition_call <- substitute(condition)
  browser()
  r <- eval(condition_call, x, parent.frame())  ## <- Point of failure
  x[r, ]
}
Run Code Online (Sandbox Code Playgroud)

直接调用subset2()

当用户subset2()直接呼叫时, condition_call <- substitute(condition)分配给condition_call包含未评估呼叫的"呼叫"对象a >= 4.此调用到传递 eval(expr, envir, enclos),这需要作为第一个参数,其值的类的对象的符号call,nameexpression.到现在为止还挺好.

subset2(sample_df, a >= 4)
## Called from: subset2(sample_df, a >= 4)
Browse[1]> is(condition_call)
## [1] "call"     "language"
Browse[1]> condition_call
## a >= 4
Run Code Online (Sandbox Code Playgroud)

eval()现在设置工作,搜索包含在expr=condition_callfirst in envir=x和then(如果需要)in enclos=parent.frame()及其封闭环境中的任何符号的值.在这种情况下,它会aenvir=x(和符号>=package:base)中找到符号并成功完成评估.

Browse[1]> ls(x)
## [1] "a" "b" "c"
Browse[1]> get("a", x)
## [1] 1 2 3 4 5
Browse[1]> eval(condition_call, x, parent.frame())
## [1] FALSE FALSE FALSE  TRUE  TRUE
Run Code Online (Sandbox Code Playgroud)

从subscramble()中调用subset2()

在体内subscramble(),subset2()被称为: subset2(x, condition).充实,这个电话真的相当于subset2(x=x, condition=condition).因为它提供的 参数(即传递给命名形式参数 的值condition)是表达式condition,所以 condition_call <- substitute(condition)赋值给condition_call符号对象condition.(理解这一点对于准确理解嵌套调用如何失败非常关键.)

因为eval()很高兴有一个符号(又名"名称")作为它的第一个参数,再次到目前为止这么好.

subscramble(sample_df, a >= 4)
## Called from: subset2(x, condition)
Browse[1]> is(condition_call)
## [1] "name"      "language"  "refObject"
Browse[1]> condition_call
## condition
Run Code Online (Sandbox Code Playgroud)

现在eval()开始寻找未解决的符号 condition.envir=x(data.frame sample_df)中没有列匹配,因此它继续进行.enclos=parent.frame()由于相当复杂的原因,该环境最终成为调用的评估框架subscramble().在那里,它确实找到名为对象condition.

Browse[1]> ls(x)
## [1] "a" "b" "c"
Browse[1]> ls(parent.frame()) ## Aha! Here's an object named "condition"
## [1] "condition" "x"
Run Code Online (Sandbox Code Playgroud)

重要的是,事实证明condition,调用堆栈上的几个对象在调用的环境之上browser().

Browse[1]> sys.calls()
# [[1]]
# subscramble(sample_df, a >= 4)
# 
# [[2]]
# scramble(subset2(x, condition))
# 
# [[3]]
# subset2(x, condition)               
# 
Browse[1]> sys.frames()
# [[1]]
# <environment: 0x0000000007166f28>   ## <- Envt in which `condition` is evaluated
# 
# [[2]]
# <environment: 0x0000000007167078>
# 
# [[3]]
# <environment: 0x0000000007166348>   ## <- Current environment


## Orient ourselves a bit more
Browse[1]> environment()            
# <environment: 0x0000000007166348>
Browse[1]> parent.frame()           
# <environment: 0x0000000007166f28>

## Both environments contain objects named 'condition'
Browse[1]> ls(environment())
# [1] "condition"      "condition_call" "x"             
Browse[1]> ls(parent.frame())
# [1] "condition" "x"  
Run Code Online (Sandbox Code Playgroud)

要检查condition找到的对象eval()(其中的parent.frame()那个,结果是评估框架subscramble())需要特别小心.我用过recover() ,pryr::promise_info()如下图所示.

那次检查表明这condition是一个承诺,其表达槽是a >= 4环境的.GlobalEnv.a到目前为止,我们对has的搜索已经过去了sample_df(其中找到了值a),因此表达式槽的评估失败(除非在搜索路径的更远处或其他地方a找到了一个名为的对象.GlobalEnv).

Browse[1]> library(pryr) ## For is_promise() and promise_info()  
Browse[1]> recover()
# 
# Enter a frame number, or 0 to exit   
# 
# 1: subscramble(sample_df, a >= 4)
# 2: #2: scramble(subset2(x, condition))
# 3: #1: subset2(x, condition)
# 
Selection: 1
# Called from: top level 
Browse[3]> is_promise(condition)
# [1] TRUE
Browse[3]> promise_info(condition)
# $code
# a >= 4
# 
# $env
# <environment: R_GlobalEnv>
# 
# $evaled
# [1] FALSE
# 
# $value
# NULL
# 
Browse[3]> get("a", .GlobalEnv)
# Error in get("a", .GlobalEnv) : object 'a' not found
Run Code Online (Sandbox Code Playgroud)

对于一个多的证据上诺言物体condition被发现中enclos=parent.frame(),一个可以指向enclos其他地方更远了搜索路径,这样parent.frame()在过程中跳过condition_call的评价.当一个人这样做时,subscramble()再次失败,但这次发出的信息 condition本身并未找到.

## Compare
Browse[1]> eval(condition_call, x, parent.frame())
# Error in eval(expr, envir, enclos) (from #4) : object 'a' not found

Browse[1]> eval(condition_call, x, .GlobalEnv)
# Error in eval(expr, envir, enclos) (from #4) : object 'condition' not found
Run Code Online (Sandbox Code Playgroud)


Sea*_*phy 6

这是一个棘手的问题,所以感谢这个问题.该错误与替换在参数调用时的行为方式有关.如果我们查看来自substitute()的帮助文本:

通过检查解析树的每个组件进行替换,如下所示:如果它不是env中的绑定符号,则它将保持不变.如果它是一个promise对象,即函数的形式参数或使用delayedAssign()显式创建的,则promise的表达式槽替换该符号.

这意味着当你condition在嵌套的subset2函数中进行评估时,substitute设置condition_call为未评估的'condition'参数的promise对象.由于promise对象非常模糊,定义如下:http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects

关键点是:

Promise对象是R的懒惰评估机制的一部分.它们包含三个槽:值,表达式和环境.

访问参数时,将在存储的环境中计算存储的表达式,并返回结果

基本上,在嵌套函数中,condition_call设置为promise对象condition,而不是替换其中包含的实际表达式condition.因为promise对象"记住"它们来自的环境,所以它似乎会覆盖 - 的行为eval()- 所以无论eval()的第二个参数如何,condition_call都会在父环境中评估参数的传递,其中没有'一个'.

您可以创建promise对象delayedAssign()并直接观察它:

delayedAssign("condition", a >= 4)
substitute(condition)
eval(substitute(condition), sample_df)
Run Code Online (Sandbox Code Playgroud)

您可以看到substitute(condition)它不会返回a >= 4,但只是简单地说condition,并且尝试在环境中进行评估sample_df失败,就像在Hadley的示例中那样.

希望这很有用,我相信其他人可以进一步澄清.