以下是哈德利高级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
哈德利解释说:
你能看出问题所在吗? condition_call包含表达式条件 . 因此,当我们评估condition_call时,它还会计算条件,其值为a> = 4.但是,由于在父环境中没有名为a的对象,因此无法计算 .
据我所知,父env中没有 a
,但是 eval(condition_call, x, parent.frame())
evals在x中的conditional_call(用作环境的data.frame),由 parent.frame()
括起来 . 只要在x中有一个名为 a
的列,为什么会出现任何问题?
3 Answers
如果其他人偶然发现这个帖子,这里是Hadley的书中本节下面的任务#5的答案 . 它还包含对上述问题的可能的一般解决方案 .
神奇发生在
subset2
的第二行 . 在那里,substitute
收到一个明确的env
参数 . 从substitute
的帮助部分:“substitute
返回(未评估的)表达式expr
的解析树,替换env
中绑定的任何变量 . ”env
"Defaults to the current evaluation environment" . 相反,我们使用调用环境 .看看这样:
我不是100%肯定这里的解释 . 我认为这只是
substitute
的工作方式 . 在substitute
的帮助页面中:在当前环境中,
condition
是一个promise,因此表达式槽被填充,更重要的是,condition_call接收一个符号作为值 . 在调用环境中,condition
只是一个普通变量,因此值(表达式)被替换 .这是一个棘手的问题,所以感谢这个问题 . 该错误与替换在参数调用时的行为方式有关 . 如果我们查看来自substitute()的帮助文本:
这意味着当你在嵌套的subset2函数中评估
condition
时,substitute
将condition_call
设置为未评估的'condition'参数的promise对象 . 由于promise对象非常模糊,定义如下:http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects关键点是:
和
基本上,在嵌套函数中,
condition_call
设置为promise对象condition
,而不是替换condition
中包含的实际表达式 . 因为promise对象来自它们,所以它似乎会覆盖eval()
的行为 - 所以无论eval()的第二个参数如何,condition_call
都是在父环境中评估参数的传递,其中没有'a' .您可以使用
delayedAssign()
创建promise对象并直接观察:您可以看到
substitute(condition)
不会返回a >= 4
,而只是condition
,并且尝试在sample_df
环境中对其进行评估会失败,就像在Hadley的示例中一样 .希望这很有用,我相信其他人可以进一步澄清 .
tl;博士
当从
subscramble()
中调用subset2()
时,condition_call
的值是符号condition
(而不是直接调用时产生的调用a >= 4
) .subset()
对eval()
的调用首先在envir=x
(data.framesample_df
)中搜索condition
. 在那里找不到它,它接下来在enclos=parent.frame()
中搜索它找到一个名为condition
的对象 .该对象是一个promise对象,其表达式槽位为
a >= 4
,其评估环境为.GlobalEnv
. 除非在.GlobalEnv
中找到名为a
的对象或在搜索路径的后面,否则对promise的评估将失败,并显示以下消息:Error in eval(expr, envir, enclos) : object 'a' not found
.详细说明
发现错误的一个很好的方法是在
subset2()
失败的行之前插入一个browser()
调用 . 这样,我们可以直接和间接地(从另一个函数中)调用它,并检查它在第一种情况下成功的原因并在第二种情况下失败 .直接调用subset2()
当用户直接调用
subset2()
时,condition_call <- substitute(condition)
将condition_call
分配给包含未评估调用a >= 4
的"call"对象 . 此调用传递给eval(expr, envir, enclos)
,它需要作为其第一个参数的符号,该符号计算为类call
,name
或expression
的对象 . 到现在为止还挺好 .eval()
现在设置为工作,首先在envir=x
中搜索expr=condition_call
中包含的任何符号的值,然后在enclos=parent.frame()
及其封闭环境中搜索(如果需要) . 在这种情况下,它在envir=x
(和package:base
中的符号>=
)中找到符号a
并成功完成评估 .从subscramble()内调用subset2()
在
subscramble()
的主体内,subset2()
被称为:subset2(x, condition)
. 充实,这个召唤真的相当于subset2(x=x, condition=condition)
. 因为它的 supplied 参数(即传递给名为condition
的 formal 参数的值)是表达式condition
,condition_call <- substitute(condition)
将condition_call
分配给符号对象condition
. (理解这一点对于准确理解嵌套调用如何失败非常关键 . )因为
eval()
很高兴有一个符号(又名"name")作为它的第一个参数,所以再一次到目前为止一直很好 .现在
eval()
开始寻找未解决的符号condition
.envir=x
中没有列(data.framesample_df
)匹配,因此它会移至enclos=parent.frame()
由于相当复杂的原因,该环境最终成为对subscramble()
的调用的评估框架 . 在那里, does 找到一个名为condition
的对象 .重要的是,事实证明,在调用
browser()
的环境上方的调用堆栈上有几个名为condition
的对象 .要检查
eval()
找到的condition
对象(parent.frame()
中的对象,后来证明是subscramble()
的评估框架)需要特别注意 . 我使用了recover()
和pryr::promise_info()
,如下所示 .该检查显示
condition
是一个承诺,其表达槽为a >= 4
,其环境为.GlobalEnv
. 到目前为止,我们对a
的搜索移动了sample_df
(其中找到了a
的值),因此对表达式槽的评估失败(除非在.GlobalEnv
中找到名为a
的对象或在搜索路径的其他位置) .对于在
enclos=parent.frame()
中找到promise对象condition
的另一个证据,可以在搜索路径的更远处指向enclos
,以便在condition_call
的评估期间跳过parent.frame()
. 当一个人这样做时,subscramble()
再次失败,但这次是发现condition
本身未找到的消息 .