在函数内部使用 get inside lapply

发布于 2024-12-13 22:39:50 字数 1458 浏览 3 评论 0原文

这似乎是一个过于复杂的问题,但有一段时间它让我有点发疯。这也是出于好奇,因为我已经有办法做我需要的事情了,所以并不那么重要。

在 R 中,我需要一个函数来返回一个命名列表对象,其中包含所有参数和用户输入的值。为此,我编写了这段代码(玩具示例):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- frm
    for (i in 1:length(frm))
        parms[[i]] <- get(names(frm)[i])
    return(parms)
}

所以当被问到时:

> foo(b=0)

$a
[1] 1

$b
[1] 0

$h
[1] "coconut"

这个结果是完美的。问题是,当我尝试使用 lapply 来实现相同的目标,以便更加高效(和优雅)时,它并没有按照我想要的方式工作:

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get)
    names(parms) <- names(frm)
    return(parms)
}

问题显然在于get 评估其第一个参数(字符串、变量名称)的环境。我从错误消息中了解到这一点:

> foo(b=0)
Error in FUN(c("a", "b", "h")[[1L]], ...) : object 'a' not found

而且,因为在 .GlobalEnv 环境中存在具有正确名称的对象, foo 相反返回它们的值:

> a <- 100
> b <- -1
> h <- 'wallnut'
> foo(b=0)
$a
[1] 100

$b
[1] -1

$h
[1] "wallnut"

显然,作为 get 默认在 parent.frame() 中计算,它搜索 .GlobalEnv 环境中的对象,而不是当前函数的对象。这很奇怪,因为该函数的第一个版本不会发生这种情况。

我尝试了很多选项来使函数 get 在正确的环境中进行评估,但无法正确执行(我尝试过 pos=-2,0,1,2pos=-2,0,1,2 code> 和 envir=NULL 作为选项)。

如果有人碰巧比我更了解环境,特别是在这种“奇怪”的情况下,我很想知道如何解决这个问题。

感谢您抽出时间,

胡安

this may seem like a overly complicated question, but it has me driving me a little nuts for some time. It is also for curiosity, because I already have a way of doing what I need, so is not that important.

In R, I need a function to return a named list object with all the arguments and the values entered by the user. For this I have made this code (toy example):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- frm
    for (i in 1:length(frm))
        parms[[i]] <- get(names(frm)[i])
    return(parms)
}

So when this is asked:

> foo(b=0)

$a
[1] 1

$b
[1] 0

$h
[1] "coconut"

This result is perfect. The thing is, when I try to use lapply to the same goal, so as to be a little more efficient (and elegant), it does not work as I want it to:

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get)
    names(parms) <- names(frm)
    return(parms)
}

The problem clearly is with the environment in which get evaluates it's first argument (a character string, the name of the variable). This I know in part from the error message:

> foo(b=0)
Error in FUN(c("a", "b", "h")[[1L]], ...) : object 'a' not found

and also, because when in the .GlobalEnv environment there are objects with the right names, foo returns their values instead:

> a <- 100
> b <- -1
> h <- 'wallnut'
> foo(b=0)
$a
[1] 100

$b
[1] -1

$h
[1] "wallnut"

Obviously, as get by default evaluates in the parent.frame(), it searches for the objects in the .GlobalEnv environment, instead of that of the current function. This is strange, since this does not happen with the first version of the function.

I have tried many options to make the function get to evaluate in the right environment, but could not do it correctly (I've tried pos=-2,0,1,2 and envir=NULL as options).

If anyone happen to know a little more than me about environments, specially in this "strange" cases, I would love to know how to solve this.

Thanks for your time,

Juan

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

嘴硬脾气大 2024-12-20 22:39:50

编辑 2013-08-05

使用 sapply() 而不是 lapply(),大大简化了这一过程:

foo4 <- function(a=1, b=5, h='coconut') {
    frm <- formals(sys.function())
    sapply(names(frm), get, envir=sys.frame(sys.parent(0)), simplify=FALSE)
}
foo4(b=0, h='mango')

不过,这没有 sapply() lapply() 可能是更优雅的解决方案:

foo5 <- function(a=1, b=5, h='coconut') {
    modifyList(formals(sys.function()), as.list(match.call())[-1])
}
foo5(b=0, h='mango')

原始帖子 (2011-11-04)

投射后关于有点,这看起来是最好的解决方案。

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get, envir=sys.frame(sys.parent(0)))
    names(parms) <- names(frm)
    return(parms)
}
foo(b=0, h='mango')
# $a
# [1] 1

# $b
# [1] 0

# $h
# [1] "mango"

lapply 范围/评估它构造的调用的方式发生了一些微妙的事情。详细信息隐藏在对 .Internal(lapply(X, FUN)) 的调用中,但为了体验一下,请比较这两个调用:

# With function matched by match.fun, search in sys.parent(0)
foo2 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           get, envir = sys.parent(0))
}

# With anonymous function, search in sys.parent(2)    
foo3 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           FUN = function(X) get(X, envir = sys.parent(2)))
}

foo4(a=0, h='mango')
foo5(a=0, h='mango')

Edit of 2013-08-05

Using sapply() instead of lapply(), simplifies this considerably:

foo4 <- function(a=1, b=5, h='coconut') {
    frm <- formals(sys.function())
    sapply(names(frm), get, envir=sys.frame(sys.parent(0)), simplify=FALSE)
}
foo4(b=0, h='mango')

This, though, without sapply() or lapply() might be the more elegant solution:

foo5 <- function(a=1, b=5, h='coconut') {
    modifyList(formals(sys.function()), as.list(match.call())[-1])
}
foo5(b=0, h='mango')

Original post (2011-11-04)

After casting about a bit, this looks to be the best solution.

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- lapply(names(frm), get, envir=sys.frame(sys.parent(0)))
    names(parms) <- names(frm)
    return(parms)
}
foo(b=0, h='mango')
# $a
# [1] 1

# $b
# [1] 0

# $h
# [1] "mango"

There's some subtle stuff going on here with the way that lapply scopes/evaluates the calls that it constructs. The details are hidden in a call to .Internal(lapply(X, FUN)), but for a taste, compare these two calls:

# With function matched by match.fun, search in sys.parent(0)
foo2 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           get, envir = sys.parent(0))
}

# With anonymous function, search in sys.parent(2)    
foo3 <- function(a=1, h='coconut') {
    lapply(names(formals()), 
           FUN = function(X) get(X, envir = sys.parent(2)))
}

foo4(a=0, h='mango')
foo5(a=0, h='mango')
病女 2024-12-20 22:39:50

只需将当前环境转换为列表即可:

foo <- function(a=1, b=5, h='coconut') {
  as.list(environment())
}
foo(a = 0, h = 'mango')

Just convert the current environment into a list:

foo <- function(a=1, b=5, h='coconut') {
  as.list(environment())
}
foo(a = 0, h = 'mango')
彻夜缠绵 2024-12-20 22:39:50

这是改编自 @Josh O'Brien 上面的解决方案,使用 sapply 自动将正确的名称分配给结果列表(节省一行代码):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- sapply(names(frm), get, envir=sys.frame(sys.parent(-1)), simplify=FALSE)
    return(parms)
}

This is adapted from @Josh O'Brien's solution above using sapply to automatically assign the correct names to the resulting list (saves one line of code):

foo <- function(a=1, b=5, h='coconut') {
    frm <- formals(foo)
    parms <- sapply(names(frm), get, envir=sys.frame(sys.parent(-1)), simplify=FALSE)
    return(parms)
}
二手情话 2024-12-20 22:39:50

除了已经发布的可靠解决方案之外,我想我应该分享我认为发生这种情况的原因:

此问题并非特定于 lapply,而是由于调用者环境getfoo 的执行环境(绑定 frm)不同。函数 get 将首先在其调用者环境中查找命名对象,如果不存在,它将(使用默认参数 inherits = TRUE)在封闭环境中查找。

在第一个使用 for 循环的示例中,get 的调用者环境是 foo 的执行环境(因为 for 循环在其当前环境中执行),因此它是能够找到 frm 的名称。

但是,在第二个示例中,get 的调用者环境是 lapply 的执行环境。由于 frm 在那里不存在,因此 get 也在封闭环境中进行搜索。但lapply 的封闭环境是namespace:base,它本身封闭在R_GlobalEnv 中。由于 frm 在任何这些环境中都不存在,因此 get 无法找到它并引发错误。

通过实现已经发布的解决方案,您告诉 get 不要查看其调用者环境,而是专门查看 foo 的执行环境。

这是一个不使用 lapply 的玩具示例:

a <- 3

foo2 <- function(x, fun) {
  a <- 2
  out <- fun(x)
  return(out)
}

foo1 <- function(a=1) {
    print(get("a"))
    out <- foo2("a", get)
    print(out)
}

foo1()

您将看到 foo2("a", get) 返回值 2,因为这就是 foo2 中定义 a 的方式(其执行环境是 get 的调用者环境)。如果您注释掉 a <- 2 行,那么您将看到 foo2("a", get) 返回值 3。这是因为getfoo2的执行环境中找不到a,所以它接着在foo2的执行环境中寻找a foo2 的封闭环境,即 R_GlobalEnv

In addition to the solid solution already posted, I thought I'd share what I think is the reason why this is happening:

This issue is not specific to lapply, but is rather due to the caller environment for get being different than the the execution environment for foo (which binds frm). The function get will first look for the named object in its caller environment, and if it's not there it will (with the default argument inherits = TRUE) look in the enclosing environments.

In your first example with the for loop, the caller environment for get is foo's execution environment (since for loops are executed in their current environment), and so it was able to find the names for frm.

However, in your second example, the caller environment for get is instead lapply's execution environment. Since frm doesn't exist there, get also searched in the enclosing environments. But the enclosing environment for lapply is namespace:base, which is itself enclosed in R_GlobalEnv. Since frm doesn't exist in any of these environments, get cannot find it and throws an error.

By implementing the solution already posted, you are telling get to look not in its caller environment, but instead to specifically look in foo's execution environment.

Here is a toy example that does not use lapply:

a <- 3

foo2 <- function(x, fun) {
  a <- 2
  out <- fun(x)
  return(out)
}

foo1 <- function(a=1) {
    print(get("a"))
    out <- foo2("a", get)
    print(out)
}

foo1()

You'll see that foo2("a", get) returns the value 2, since this is how a is defined in foo2 (whose execution environment is the caller environment for get). If you comment out the line a <- 2, then you'll see that foo2("a", get) returns the value 3. This is because get could not find a in foo2's execution environment, so it then looked for a in foo2's enclosing environment, which is R_GlobalEnv.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文