在函数内部使用lapply中的get函数

13

这个问题可能看起来有点复杂,但让我烦恼了一段时间。这也只是出于好奇,因为我已经有了完成所需的方法,所以并不是很重要。

在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,2envir=NULL作为选项)。

如果有人在环境方面比我知道得多,特别是在这种“奇怪”的情况下,我希望知道如何解决这个问题。

谢谢您的时间,

Juan


























3个回答

12

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

1
如果foo2foo3有自己的形式参数,而不是foo的话,那就更好了 :-) - Tommy
1
给Josh:如果我使用envr=sys.parent() + 1,会有什么不同吗? - Juan
1
@Juan -- 谢谢你的原创文章,顺便说一句。它让我也疯了好几个小时! - Josh O'Brien
1
相比于 sys.frame(sys.parent(0)),我认为 environment() 更加明显。实际上,你可以用 mget(names(frm), environment()) 替换整行代码。 - hadley
1
或者你可以用 as.list(environment()) 替换整个函数。 - hadley
显示剩余3条评论

7
只需将当前环境转换为列表:
foo <- function(a=1, b=5, h='coconut') {
  as.list(environment())
}
foo(a = 0, h = 'mango')

是的,很酷,这可能正是OP真正想要的。 - Josh O'Brien

1
这是根据@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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接