以下是哈德利高级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()
.只要有一个以a
x 命名的列,为什么会有任何问题?
Jos*_*ien 15
当subset2()
从内调用subscramble()
,
condition_call
的值是符号condition
(而不是呼叫a >= 4
时,它会被直接调用该结果).首先subset()
调用eval()
搜索(data.frame ).没有找到它在那里,在它未来的搜索它确实找到名为对象
.condition
envir=x
sample_df
enclos=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()
直接呼叫时,
condition_call <- substitute(condition)
分配给condition_call
包含未评估呼叫的"呼叫"对象a >= 4
.此调用到传递
eval(expr, envir, enclos)
,这需要作为第一个参数,其值的类的对象的符号call
,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
Run Code Online (Sandbox Code Playgroud)
eval()
现在设置工作,搜索包含在expr=condition_call
first in envir=x
和then(如果需要)in 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
Run Code Online (Sandbox Code Playgroud)
在体内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)
这是一个棘手的问题,所以感谢这个问题.该错误与替换在参数调用时的行为方式有关.如果我们查看来自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的示例中那样.
希望这很有用,我相信其他人可以进一步澄清.