为什么 R 不在提供的环境父树中查找指定的对象?

Why doesn't R look up for the specified object in the provided envrionment parents tree?

配置:

OS : Windows 10 (64 bits)
R version: 3.6.3

我正在学习 R,目前正在阅读 R 中的环境。我正在做一些练习,想出了一个自己创建的示例,但似乎我仍然无法解释并正确理解在 R 中查找对象的概念。一般来说,到目前为止我所理解的(如果我错了请纠正我)是,如果 R 在当前环境中找不到对象,它会调用 in order all现有的父环境。只是为了看看它在实践中是如何工作的,我创建了以下程序:

library(rlang)
library(envnames)
library(lobstr)
e1 <- env()
e2 <- new_environment(parent = e1)
e3 <- new_environment(parent = e2)
e4 <- new_environment(parent = e3)
e5 <- new_environment(parent = e4)
e6 <- new_environment(parent = e5)
e7 <- new_environment(parent = e6)
e8 <- new_environment(parent = e7)
e9<- new_environment(parent = e8)
e10 <- new_environment(parent = e9)
e4$testvar <- 1200
e10$testfun <- function(x) {
    print(envnames::environment_name(caller_env()))
    return (testvar)
}

下面是我如何 运行 通过选择 e10 作为调用者环境

上面的程序
with(data = e10, expr = e10$testfun())

假设 testvar 在环境 e4 中定义并且 e4e10,我希望 R 在父树中从 e10 上升到 e4,以便找到 testvar 的值。但是程序因以下错误而停止:

Error in e10$testfun() (from #3) : object 'testvar' not found

你能告诉我我误解了什么吗?我使用 with(data = e10, ...) 的事实并不意味着用于函数调用的环境是 e10?

所以,这是一个异常微妙的问题。这里有两种相关的环境类型需要考虑,binding 环境,或者绑定到你的函数的环境,以及 enclosing 环境,或者创建函数的环境。在这种情况下,绑定环境是 e10,但 enclosing 环境是全局环境。来自 Hadley Wickham's Advanced R:

The enclosing environment belongs to the function, and never changes, even if the function is moved to a different environment. The enclosing environment determines how the function finds values; the binding environments determine how we find the function.

考虑以下(在执行您提供的代码后执行的)演示这一点:

eval(expression(testfun()), envir = e10)
# [1] "e10"
# Error in testfun() : object 'testvar' not found
testvar <- 600
eval(expression(testfun()), envir = e10)
# [1] "e10"
# [1] 600

此外,现在考虑:

eval(envir = e10, expr = expression(
    testfun2 <- function(x) {
        print(envnames::environment_name(caller_env()))
        return (testvar)
    }
))
eval(expression(testfun2()), envir = e10)
# [1] "e10"
# [1] 1200

我希望这能澄清问题。

更新:确定封闭和绑定环境

那么我们如何确定testfun()等函数的绑定和封闭环境?

一样,environment()函数为您提供函数的封闭环境:

environment(e10$testfun)
# <environment: R_GlobalEnv>

据我所知,基础 R 中没有简单的函数可以为您提供函数的绑定环境。如果您要查找的函数在父环境中,则可以使用 pryr::where():

pryr::where("mean")
# <environment: base>

(有一个base函数来查看一个函数是否在一个环境中,exists()pryr::where()使用它。但是,它不会通过父环境递归喜欢 where().)

但是,如果您必须搜索子环境,据我所知没有相应的功能。但是,模拟一个似乎很简单:

get_binding_environments <- function(fname) {
    ## First we need to find all the child environments to search through.
    ## We don't want to start from the execution environment;
    ## we probably want to start from the calling environment,
    ## but you may want to change this to the global environment.
    e <- parent.frame()
    ## We want to get all of the environments we've created
    objects <- mget(ls(envir = e), envir = e)
    environments <- objects[sapply(objects, is.environment)]
    ## Then we use exists() to see if the function has a binding in any of them
    contains_f <- sapply(environments, function(E) exists(fname, where = E))
    return(unique(environments[contains_f]))
}

get_binding_environments("testfun")
# [[1]]
# <environment: 0x55f865406518>

e10
# <environment: 0x55f865406518>

问题中的代码在全局环境中定义了一个函数。我们可以这样查询它的环境:

environment(e10$testfun)
## <environment: R_GlobalEnv>

当函数查找自由变量(被引用但未在函数中定义的变量)时,例如 testvar,它会使用函数的环境(和祖先)来查找变量。 testvare4 中,但 e4 不是全局环境的祖先,因此在问题中找不到 testvar

其他环境无关紧要。如果调用该函数,则调用者的环境(也称为父框架)在变量查找中根本没有任何作用。同样,如果该函数后来被放置在其他地方(在本例中为 e10),则该环境也不会参与变量查找。此外,意识到当问题中的函数被放入 e10 时,它已经在全局环境中定义,因此全局环境已经被设置为它的环境。函数由参数、主体和环境(以及 class 等属性)组成,将函数移动到其他地方不会改变任何这些成分。

修复

如果我们想让 testfun 的环境成为它的环境并因此将 e4 作为祖先:

environment(e10$testfun) <- e10

或者,我们无法首先在全局环境中定义 testfun,而是从一开始就在 e10 环境中定义 testfun

with(e10, {
  testfun <- function() testvar
})

e10$testfun()
## [1] 1200

函数名称

另一个混淆点可能是误以为下面的语句定义了e10中名为testfun的函数。

e10$testfun <- function() testvar

这个想法的问题是函数没有名字。函数的三个组成部分是参数、主体和环境(以及 class 和可能的 srcref 和 scrfile 等属性)。该名称不是函数的组成部分。可以将一个函数放在一个变量中并引用该变量,就好像它是函数的名称一样,但实际上它只是一个保存函数的变量,名称不是函数本身的一部分。因此,在上面的代码行中,我们没有定义名为 testfun 的函数;相反,我们正在定义一个匿名函数(在全局环境中),然后将其移动到变量 testfun.

R 本身的一个例子

虽然用户创建的函数通常保留在定义它们的环境中,例如

f <- function() "hello"

# the environment of f
environment(f)
## <environment: R_GlobalEnv>

# the environment where f is located (same)
as.environment(find("f"))
## <environment: R_GlobalEnv>

搜索路径上的 R 包中的函数

# show search path
search()

通常位于他们的环境中。对于搜索路径上包中的任何函数,该函数将以包的命名空间作为其环境,但是当您访问该函数时,它将被发现,而不是在命名空间中,而是在不同的环境中。

# the environment of function mean
e1 <- environment(mean); e1
## <environment: namespace:base>

# where mean is located
e2 <- as.environment(find("mean")); e2
## <environment: base>

# these are NOT the same
identical(e1, e2)
## [1] FALSE

此博客中的图表很好地说明了这一点 post:http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

原型

有一个包可以按照问题预期的方式工作。 proto 对象就像环境,但如果您为它们分配一个函数,那么该函数的环境将更改为它们分配给的 proto object/environment。 (还有其他差异,但我们重点关注这一点。)

首先我们定义一个原型对象p,其父对象是e9,然后将感兴趣的函数赋值给p。最后我们运行那个函数。 (第一个参数隐式是 proto 对象,所以我们忽略了它。)我们看到函数确实重置了它的环境,并且 e4 现在是它的环境的祖先,没有明确设置它。

library(proto)

p <- proto(e9)  # define proto object whose parent is e9
p$testfun <- function(self) testvar

identical(p, with(p, environment(testfun)))  # testfun's environment is now p
## [1] TRUE

p$testfun()
## [1] 1200