在函数内部使用 get inside lapply
这似乎是一个过于复杂的问题,但有一段时间它让我有点发疯。这也是出于好奇,因为我已经有办法做我需要的事情了,所以并不那么重要。
在 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
pos=-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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
编辑 2013-08-05
使用
sapply()
而不是lapply()
,大大简化了这一过程:不过,这没有
sapply()
或lapply()
可能是更优雅的解决方案:原始帖子 (2011-11-04)
投射后关于有点,这看起来是最好的解决方案。
lapply
范围/评估它构造的调用的方式发生了一些微妙的事情。详细信息隐藏在对.Internal(lapply(X, FUN))
的调用中,但为了体验一下,请比较这两个调用:Edit of 2013-08-05
Using
sapply()
instead oflapply()
, simplifies this considerably:This, though, without
sapply()
orlapply()
might be the more elegant solution:Original post (2011-11-04)
After casting about a bit, this looks to be the best solution.
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:只需将当前环境转换为列表即可:
Just convert the current environment into a list:
这是改编自 @Josh O'Brien 上面的解决方案,使用
sapply
自动将正确的名称分配给结果列表(节省一行代码):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):除了已经发布的可靠解决方案之外,我想我应该分享我认为发生这种情况的原因:
此问题并非特定于
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
的玩具示例:您将看到
foo2("a", get)
返回值2
,因为这就是foo2
中定义a
的方式(其执行环境是get
的调用者环境)。如果您注释掉a <- 2
行,那么您将看到foo2("a", get)
返回值3
。这是因为get
在foo2
的执行环境中找不到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 forget
being different than the the execution environment forfoo
(which bindsfrm
). The functionget
will first look for the named object in its caller environment, and if it's not there it will (with the default argumentinherits = TRUE
) look in the enclosing environments.In your first example with the for loop, the caller environment for
get
isfoo
's execution environment (since for loops are executed in their current environment), and so it was able to find the names forfrm
.However, in your second example, the caller environment for
get
is insteadlapply
's execution environment. Sincefrm
doesn't exist there,get
also searched in the enclosing environments. But the enclosing environment forlapply
isnamespace:base
, which is itself enclosed inR_GlobalEnv
. Sincefrm
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 infoo
's execution environment.Here is a toy example that does not use
lapply
:You'll see that
foo2("a", get)
returns the value2
, since this is howa
is defined infoo2
(whose execution environment is the caller environment forget
). If you comment out the linea <- 2
, then you'll see thatfoo2("a", get)
returns the value3
. This is becauseget
could not finda
infoo2
's execution environment, so it then looked fora
infoo2
's enclosing environment, which isR_GlobalEnv
.