我在R的词法范围内遇到了一个奇怪的(对我来说)行为,这是因为首先将NULL环境附加到搜索路径,如 attach()的帮助文件中所示,然后使用 sys.source()填充它。
以下是该问题的简化且可重现的示例。我有3个函数(f1,f2和f3)在三个单独的文件中我希望附加到三个独立的环境(分别是env.A,env.B和env.C)。这是设置功能:
setup <- function() {
for (i in sprintf('env.%s',LETTERS[1:3])) if (i%in%search())
detach(i, unload=TRUE, force=TRUE, character.only=TRUE) # detach existing to avoid duplicates
env.A = attach(NULL, name='env.A')
env.B = attach(NULL, name='env.B')
env.C = attach(NULL, name='env.C')
sys.source('one.R', envir=env.A)
sys.source('two.R', envir=env.B)
sys.source('three.R', envir=env.C)
}
setup()
调用此函数后,将创建3个新环境,其中包含每个环境中包含的函数f1,f2和f3。每个函数都存在于3个独立文件中的一个:“one.R”,“two.R”和“three.R”。功能很简单:
f1 <- function() {
print('this is my f1 function')
return('ok')
}
f2 <- function() {
f1()
f3()
print('this is my f2 function')
return('ok')
}
f3 <- function() {
print('this is my f3 function')
return('ok')
}
如您所见,函数f1和f3没有依赖关系,但函数f2依赖于f1和f2。致电搜索()会显示以下内容:
[1] ".GlobalEnv" "env.C" "env.B"
[4] "env.A" "package:stats" "package:graphics"
[7] "package:grDevices" "package:utils" "package:datasets"
[10] "package:methods" "Autoloads" "package:base"
调用f2,提供以下内容:
> f2()
[1] "this is my f1 function"
Error in f2() : could not find function "f3"
显然f2可以“看到”f1,但找不到f3。置换附加环境的顺序使我得出结论,搜索路径的顺序是至关重要的。搜索路径中较低的功能是可见的,而未找到函数调用位置的“上游”功能。
在这种情况下,f2(env.B)找到f1(env.A),但找不到f3(env.C)。这与我理解R的范围规则(至少我认为我理解它)相反。我的理解是R首先检查本地环境,然后是封闭环境,然后检查任何其他封闭环境,然后沿着搜索工作,从“.GlobalEnv”开始,直到它找到第一个匹配的相应(函数/对象)名称。如果它一直到“R_empty_env”然后返回“找不到函数”错误。这显然不是在这个简单的例子中发生的。
发生了什么事?为什么R不会遍历整个搜索路径并找到位于 env.C 中的 f3 ?我假设在进行附加调用时幕后会发生一些事情。也许某些属性设置详细依赖?我找到了一个没有遇到此问题的解决方法,我创建并填充环境之前以附加它。使用伪代码:
env.A <- new.env(); ... B ... C
sys.source('one.R', envir=env.A)
...
attach(env.A)
...
这种解决方法表现出与我的期望一致的行为,但我对这种差异感到困惑:附加然后填充与填充然后附加。
评论,解释,想法非常感谢。感谢。
答案 0 :(得分:1)
两种方法之间的差异与每个新创建的环境的父环境有关。
当R找到一个对象时,它将尝试解析该环境中的所有变量。如果找不到它们,它将在父环境中查找下一个。它将继续这样做,直到它一直到空的环境。因此,如果将一个函数作为全局环境作为父环境,那么搜索路径中的每个环境都将按照您的预期进行搜索。
使用
创建环境时env.A <- new.env();
parent=
参数的默认值为parent.frame()
,因此当您调用它时,它会将值设置为当前environment()
值。观察
parent.env(env.A)
# <environment: R_GlobalEnv>
是全球环境的孩子。但是,当你这样做时
env.A = attach(NULL, name='env.A')
parent.env(env.A)
# <environment: 0x1089c0ea0>
# attr(,"name")
# [1] "tools:RGUI"
您将看到它将父级设置为上次加载的搜索路径中的环境(恰好是&#34;工具:RGUI&#34;在重新启动R后重新启动。)并继续
env.B = attach(NULL, name='env.B')
parent.env(env.B)
#<environment: 0x108a2edf8>
#attr(,"name")
#[1] "env.A"
env.C = attach(NULL, name='env.C')
parent.env(env.C)
# <environment: 0x108a4f6e0>
# attr(,"name")
# [1] "env.B"
请注意,当我们继续通过attach()
添加环境时,他们没有GlobalEnv
的父级。这意味着,一旦我们将变量解析为env.B
,它就没有办法了解链条&#34;到env.A
。这就是找不到f3()
的原因。这与做
env.A <- new.env(parent=parent.env(globalenv()));
env.B <- new.env(parent=env.A);
env.C <- new.env(parent=env.B);
显式调用new.env
。
请注意,如果我将附件的顺序切换为
env.C = attach(NULL, name='env.C')
env.B = attach(NULL, name='env.B')
env.A = attach(NULL, name='env.A')
并尝试运行f2()
,这次它无法找到f1()
,因为它只能在链的一个方向上运行。
因此,创建环境的两种不同方式在分配默认父环境的方式上有所不同。因此,在这种情况下,attach(NULL)
方法可能并不适合您。
答案 1 :(得分:0)
我同意答案似乎在attach()
和new.env()
之间的默认父作业有所不同。我发现有点奇怪的是attach()
默认情况下会在搜索列表中第二个将父母分配给环境,但它就是这样,它背后可能有一个合理的原因。解决方案很简单:
env.A <- attach(NULL, name='env.A')
parent.env(env.A) <- .GlobalEnv
在使用new.env()
的备用解决方案中,有一个小警告,您没有遇到因为您直接在.GlobalEnv中工作,但在OP中,我在临时环境中工作(&#34;设置&#34;功能)。因此new.env()
调用的父框架实际上是此setup
环境。见下文:
setup <- function() {
env.A <- new.env(); env.B <- new.env(); env.C <- new.env()
print(parent.env(environment()))
print(parent.frame())
print(environment())
print(parent.env(env.A))
print(parent.env(env.B))
print(parent.env(env.C))
}
setup()
#<environment: R_GlobalEnv>
#<environment: R_GlobalEnv>
#<environment: 0x2298368>
#<environment: 0x2298368>
#<environment: 0x2298368>
#<environment: 0x2298368>
从命令行调用setup()
时,请注意其父级为.GlobalEnv
,父帧也是如此。但是,环境A-C的父级是临时setup
环境(0x2298368)。当setup()
完成时,其环境将关闭并被删除,并且env.A-C将成为孤儿。此时(我假设)R将父母重新分配给.GlobalEnv
,这就是为什么这个替代方案有效。
我认为更清洁的方式不会依赖于.GlobalEnv
的正确重新分配并直接指定它:env.A <- new.env(parent=.GlobalEnv)
。这在我的测试用例中运行良好...我们将看到当我扩展到~750个相互依赖的函数时会发生什么!
再次感谢你的明确回答,我已经投了票,但我显然太新了,没有这个特权。