1

配置:

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

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

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?

4

2 回答 2

2

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

封闭环境属于函数,并且永远不会改变,即使函数被移动到不同的环境。封闭环境决定了函数如何查找值;绑定环境决定了我们如何找到函数。

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

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()

正如G. Grothendieck 的回答所示,该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>
于 2020-05-03T00:37:44.937 回答
1

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

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

当一个函数查找一个自由变量(一个被引用但未在函数中定义的变量)时,例如testvar它使用函数的环境(和祖先)来查找变量。 testvar是在e4e4不是全球环境的祖先,所以在这个问题testvar中没有找到。

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

定影

如果我们希望它作为其环境并因此作为祖先,我们需要显式设置testfunto的环境:e10e4

environment(e10$testfun) <- e10

或者,或者,我们不能一开始就testfun在全局环境中定义,而是从一开始就testfune10环境中定义:

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

e10$testfun()
## [1] 1200

函数名称

另一个混淆点可能是误解,认为以下语句定义了 in 中命名的testfun函数e10

e10$testfun <- function() testvar

这个想法的问题是函数没有名字。函数的三个组成部分是参数、主体和环境(以及属性,例如类,可能还有 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

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

原型

有一个包以问题似乎期望的方式工作。proto 对象就像环境,但是如果您为它们分配一个函数,那么函数的环境将更改为分配给它们的 proto 对象/环境。(还有其他差异,但我们专注于这一点。)

首先我们定义一个父对象为 e9 的 proto 对象p,然后将感兴趣的函数分配给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
于 2020-05-03T00:50:07.023 回答