在函数内部使用 lapply 的 get

问题描述 投票:0回答:4

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

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

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

感谢您的宝贵时间,

胡安

r lapply
4个回答
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')

7
投票

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

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

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

0
投票

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

此问题并非特定于

lapply
,而是由于
get
的调用者环境与
foo
的执行环境(绑定
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
,因为这就是
a
中定义的方式(其执行环境是
foo2
的调用者环境)。如果您注释掉行
get
,那么您将看到
a <- 2
返回值
foo2("a", get)
。这是因为
3
get
的执行环境中找不到
a
,所以它接着在
foo2
的封闭环境中寻找
a
,即
foo2
    

© www.soinside.com 2019 - 2024. All rights reserved.