以下是哈德利高级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())
在x(用作环境的数据框架)中包含了条件调用,由parent.frame()
括起来。只要x中有一个名为a
的列,为什么会有任何问题?
答案 0 :(得分:15)
从subset2()
内调用subscramble()
时,
condition_call
的值是符号condition
(而不是
在直接调用时调用a >= 4
。 subset()
的
致电eval()
首先在condition
({1}}中搜索envir=x
data.frame sample_df
)。在那里找不到它,它接下来在enclos=parent.frame()
中搜索 找到一个名为的对象
condition
。
该对象是一个promise对象,其表达式槽是
a >= 4
,其评估环境为.GlobalEnv
。除非
名为a
的对象位于.GlobalEnv
或搜索的后面
路径,承诺的评估随着观察到的消息而失败
那: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, ]
}
失败。这样,我们可以直接和间接地称呼它(来自
在另一个函数中),并检查它在第一种情况下成功的原因
在第二次失败。
subset2()
当用户直接拨打condition_call <- substitute(condition)
时,
condition_call
将a >= 4
分配给“调用”对象
包含未评估的电话eval(expr, envir, enclos)
。此调用将传入
call
,需要作为第一个参数a
评估为类name
,expression
或对象的对象的符号
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
。到目前为止一切都很好。
eval()
expr=condition_call
现在开始工作,搜索任何符号的值
envir=x
首先包含在enclos=parent.frame()
中,然后(如果
需要)a
及其封闭的环境。在
在这种情况下,它会在envir=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
)并成功完成评估。
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
很乐意将符号(也称为“名称”)作为第一个符号
争论,到目前为止一直如此好。
eval()
现在condition
开始搜索未解析的符号
envir=x
。 sample_df
中没有列(data.frame 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"
的对象。
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"
的对象。
condition
要检查eval()
找到的parent.frame()
对象(subscramble()
中的对象,后来证明是recover()
的评估框架)需要特别小心。我用了pryr::promise_info()
和condition
如下所示。
该检查显示a >= 4
是一个承诺,其表达式为.GlobalEnv
并且其中
环境是a
。到目前为止,我们对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
中找到名为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
再次失败,但这一次发出了消息
{{1}}本身未找到。
{{1}}
答案 1 :(得分:6)
这是一个棘手的问题,所以感谢这个问题。该错误与替换在参数调用时的行为方式有关。如果我们查看来自substitute()的帮助文本:
通过检查解析树的每个组件进行替换,如下所示:如果它不是env中的绑定符号,则不变。 如果它是一个promise对象,即函数的形式参数或使用delayedAssign()显式创建的,则promise的表达式插槽将替换该符号。
这意味着,当您在嵌套的subset2函数中评估condition
时,substitute
会将condition_call
设置为未评估条件的承诺对象&#39;论点。由于promise对象非常模糊,因此定义如下:http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects
关键点是:
Promise对象是R懒惰评估机制的一部分。它们包含三个槽:值,表达式和环境。
和
访问参数时,将在存储的环境中计算存储的表达式,并返回结果
基本上,在嵌套函数中,condition_call
设置为promise对象condition
,而不是condition
中包含的实际表达式的替换。因为承诺对象记得&#39;它们来自的环境,似乎这会覆盖eval()
的行为 - 所以无论eval()的第二个参数如何,condition_call
都在父环境中进行评估,参数是从该环境中传递的,其中没有&#39; a&#39;
您可以使用delayedAssign()
创建承诺对象并直接观察:
delayedAssign("condition", a >= 4)
substitute(condition)
eval(substitute(condition), sample_df)
您可以看到substitute(condition)
没有返回a >= 4
,只是condition
,并且试图在sample_df
的环境中评估它会失败,就像在Hadley& #39; s例子。
希望这很有帮助,我相信其他人可以进一步澄清。
答案 2 :(得分:1)
如果其他人偶然发现这个帖子,这里是Hadley的书中本节下面的任务#5的答案。它还包含对上述问题的可能的一般解决方案。
subset2 <- function(x, condition, env = parent.frame()) {
condition_call <- substitute(condition, env)
r <- eval(condition_call, x, env)
x[r, ]
}
scramble <- function(x) x[sample(nrow(x)), ]
subscramble <- function(x, condition) {
scramble(subset2(x, condition))
}
subscramble(sample_df, a >= 3)
神奇发生在subset2
的第二行。在那里,substitute
收到一个明确的env
参数。在substitute
的帮助部分中:&#34; substitute
返回(未评估的)表达式expr
的解析树,替换env
中绑定的任何变量。&#34 ; env
&#34;默认为当前评估环境&#34;。相反,我们使用调用环境。
像这样检查出来:
debugonce(subset2)
subscramble(sample_df, a >= 3)
Browse[2]> substitute(condition)
condition
Browse[2]> substitute(condition, env)
a >= 3
我不是100%肯定这里的解释。我认为这只是substitute
的工作方式。在substitute
的帮助页面中:
通过检查解析树的每个组件来进行替换 如下:(...)如果它是一个承诺对象,即一个正式的论点 一个函数或使用
delayedAssign()
显式创建的 promise的表达式槽替换了符号。如果是的话 普通变量,其值被替换为(...)。
在当前环境中,condition
是一个promise,因此表达式槽被填充,更重要的是,condition_call接收一个符号作为值。在调用环境中,condition
只是一个普通变量,因此值(表达式)被替换。