问题描述
我有一个要计算的表达式expr
;我需要评估的符号/值对可能在三种环境中的一种(或多种!)中,我不确定是哪一种。我想找到一种方便,有效的方式来“链接”环境。 有没有一种安全的方法可以避免复制环境内容?
这是设置:
env1 <- list2env(list(a=1))
env2 <- list2env(list(b=1))
env3 <- list2env(list(c=1))
expr <- quote(a+b)
因此,我需要在环境expr
和env1
的组合中评估env2
(但我不一定知道)。 eval(expr,env1)
中的任何一个; eval(expr,env2)
;和eval(expr,env3)
将失败,因为这些环境中没有一个包含所有必需的符号。
假设我愿意假设符号是env1
+ env2
中的 或env1
+中的 env3
。我可以:
- 按照this question中所述为每个配对创建组合环境。
问题:
替换函数parent.env
(尽管根据资料来源的历史,关于“在不久的将来”被移除的警告至少已使用19年了……)
(事实上,我已经设法使用这种方法来引发一些无限循环)
- 使用
tryCatch(eval(call,envir=as.list(expr1),enclos=expr2),error=function(e) {
tryCatch(eval(call,as.list(expr1),enclos=expr3))
创建“环境中的环境”;一次尝试组合的一对,看看哪个可行。请注意,enclos=
仅在envir
是列表或配对列表时有效,这就是为什么我必须使用as.list()
的原因。
问题:我认为我仍然最终将expr1
的内容复制到新环境中。
我可以使用更深层嵌套的tryCatch()
子句集合来一次尝试使用环境,然后再复制它们,这将有助于避免在不必要的地方复制(但看起来很笨拙)。
解决方法
将环境转换为列表,将它们连接起来,并将其用作eval
的第二个参数。请注意,这不会修改环境本身。
L <- do.call("c",lapply(list(env1,env2,env3),as.list))
eval(expr,L)
## [1] 2
还请注意,这不会复制a
,b
和c
的内容。它们仍然位于原始地址:
library(pryr)
with(env1,address(a))
## [1] "0x2029f810"
with(L,address(a))
## [1] "0x2029f810"
,
不,没有简单的方法可以链接环境。如您所知,每个环境都有一个父环境,即另一个环境,因此整个环境形成一个树形结构。 (树的根是空的环境。)您不能轻易地从树上摘下一片叶子并将其嫁接到另一片叶子上,而无需对其结构进行更改。
因此,如果您真的需要以描述的方式评估表达式,则将需要对其进行解析,自己查找名称并将值替换为表达式。但这甚至不一定一定会在最后为您带来相同的价值,因为substitute()
和类似的功能可能会涉及其中。
我的建议是重新开始,不要尝试像您在说的那样做一个表达。这可能涉及复制,但是请记住,在R中复制通常很便宜:仅当您修改其中一个副本时,成本才会出现。
编辑后添加:
当前其他四个答案隐含地假设env1
至env3
与您的示例一样简单。如果是这样,那么我将使用@ G.Grothendieck的解决方案。但是所有人都无法在您的示例中进行这种简单的改动:
env1 <- list2env(list(a=1))
env2parent <- list2env(list(b=1))
env2 <- new.env(parent = env2parent)
env3 <- list2env(list(c=1))
expr <- quote(a+b)
我可以使用quote(b)
来评估eval(quote(b),envir = env2)
,但是不能使用其他解决方案来评估expr
,除非在通过的环境列表中也包括了env2parent
。
再次编辑:
这里的解决方案基本上可以执行我建议的操作,除了解析之外,它使用@ r2evans答案之一中的all.vars
函数。它通过将所有变量复制到一个公共环境中来工作,因此会进行复制,但名称会保留:
envfunc3 <- function(expr,...) {
vars <- all.vars(expr)
env <- new.env()
for (v in vars) {
for (e in list(...))
if (exists(v,envir = e)) {
assign(v,get(v,envir = e),envir = env)
break
}
}
eval(expr,envir=env)
}
,
另一种策略:临时重建父级环境链,使用R的自然搜索顺序,然后将其改回。
我认识到“将来”的提法,并且您不鼓励使用parent.env
,但是...它仍然非常有效。我相信使用它的大多数“风险”(因此,在文档中令人沮丧的评论)是,更改而不是不更改会引入很多方法来破坏它们。我确实在这里看到了一些脆弱性,因为我对expr
的假设是它相对“简单”。如果存在(例如)依赖于C库的活动绑定,则可能会引起问题。
尽管如此...
envfunc <- function(expr,...) {
envs <- list(...)
if (length(envs) > 1) {
parents <- lapply(envs,parent.env)
on.exit({
for (i in seq_along(envs)) parent.env(envs[[i]]) <- parents[[i]]
},add = TRUE)
for (i in seq_along(envs)[-1]) parent.env(envs[[i]]) <- envs[[i-1]]
}
eval(expr,envir = envs[[ length(envs) ]])
}
str(list(env1,env3))
# List of 3
# $ :<environment: 0x0000000099932bc8>
# $ :<environment: 0x0000000099931d58>
# $ :<environment: 0x00000000445b97c0>
str(lapply(list(env1,parent.env))
# List of 3
# $ :<environment: 0x000000000787d7a8>
# $ :<environment: 0x000000000787d7a8>
# $ :<environment: 0x000000000787d7a8>
str(lapply(list(env1,function(e) lapply(e,address)))
# List of 3
# $ :List of 1
# ..$ a: chr "00000000bb23c350"
# $ :List of 1
# ..$ b: chr "00000000bb23c1c8"
# $ :List of 1
# ..$ c: chr "00000000bb23c040"
envfunc(expr,env1,env3)
# [1] 2
str(list(env1,address)))
# List of 3
# $ :List of 1
# ..$ a: chr "00000000bb23c350"
# $ :List of 1
# ..$ b: chr "00000000bb23c1c8"
# $ :List of 1
# ..$ c: chr "00000000bb23c040"
这有效地产生了环境的链接列表,这意味着环境的顺序提供了事项。在此示例中,没有重复,但是不难想象它会产生影响。
envfunc(expr,env3)
# [1] 2
env1$b <- 99
envfunc(expr,env3)
# [1] 2
env3$b <- 99
envfunc(expr,env3)
# [1] 100
,
另一个选项,与另一个完全不同:主动绑定?我可能会继续努力...
envfunc2 <- function(expr,...) {
vars <- all.vars(expr)
env <- environment()
for (e in list(...)) {
vars_in_e <- intersect(vars,names(e))
vars <- setdiff(vars,vars_in_e)
for (v in vars_in_e) makeActiveBinding(v,local({ v=v; e=e; function() get(v,envir = e); }),env)
}
eval(expr)
}
envfunc2(expr,env3)
# [1] 2
这包括实时get
从其各自环境中设置值的开销。
我认为attach()
确实是您想要的,为什么它是最令人讨厌的R函数与您的情况无关,我们可以构建一个仔细的包装器来分离所有on.exit
,应该安全:
eval_with_envs <- function(expr,...) {
dots <- substitute(...())
on.exit(
for (env in dots) {
if(as.character(env) %in% search())
eval.parent(bquote(detach(.(env))))
}
)
for (env in dots) {
eval.parent(bquote(attach(.(env))))
}
eval.parent(expr)
}
eval_with_envs(expr,env3)
#> [1] 2