R中with()和attach()之间的行为截然不同?

时间:2011-09-20 13:24:11

标签: r

人们经常使用attach()detach()函数为R中的变量名设置“搜索路径”,但因为这会改变难以跟踪的全局状态,people推荐using with() instead,它会在单个表达式的持续时间内对搜索路径进行临时更改。

但是我注意到,与attach()不同, with()显然没有“解决”功能。例如,让我们首先设置一个虚拟函数来访问名为x的变量:

f <- function { print(x) }

现在,

with(list(x=42), f())
即使

也会失败

with(list(x=42), print(x))

attach(list(x=42))
f()

都成功了! :(

谁能告诉我为什么?我希望with()的行为与此处的attach()完全相同,以便通过设置包含with()参数值的环境,使我能够有效地将大参数列表传递给函数。我认为这种方法比替代方案有几个好处(我考虑过的两个方法是(a)费力地将所有参数传递给函数,以及(b)明确地将参数列表/框架作为函数参数传递并具有函数本身调用with()),但它不起作用。老实说,我发现这种差异非常麻烦!任何解释/帮助将不胜感激。

我正在使用R 2.11.1。

4 个答案:

答案 0 :(得分:8)

with(list(x = 42), f())正在做什么和你期望的区别是 lexical scoping (这是R使用的)和动态范围之间的区别(似乎是你所期待的)。

词法范围意味着自由变量(如x中的变量f)会在f 定义的环境中查找< / em> - 不是环境f调用的。

f在全局环境中定义,因此查找x

调用with来创建一个从中调用f的新环境并不重要,因为调用它的环境不涉及查找自由变量。

要使其按照您想要的方式工作,请创建f的副本并重置其环境,因为这是R用于搜索自由变量的内容:

with(list(x = 42), { environment(f) <- environment(); f() })

不可否认,这有点繁琐,但你可以通过使用proto包来简化它,因为proto重置了显式插入到proto对象中的每个函数的环境:

library(proto)
with(proto(x = 42, f = f), f())

增加:

请注意,如果您的目标是进行面向对象的编程(根据您对其他响应的评论),那么您可能希望在proto home page进一步查看proto。例如,我们可以定义proto对象p并重新定义f,使其成为p的方法(在这种情况下它必须接受参数1中的对象),如下所示:

library(proto)
p <- proto(x = 42, f = function(.) print(.$x))
p$f()

增加2:

对于附加的案例,首先运行f()在全局环境中查找,因为这是定义f的地方。由于在全局环境中找不到x,因此它会查看全局环境的父级,在这种情况下,它会在那里找到它。我们可以使用parent.env发现全局环境的父级,在这里我们看到附加环境已成为全局环境的父级。

> attach(list(x = 42))
> parent.env(.GlobalEnv)
<environment: 0x048dcdb4>
attr(,"name")
[1] "list(x = 42)"

我们可以按照以下顺序查看全球环境及其所有祖先:

> search()
 [1] ".GlobalEnv"        "list(x = 42)"      "package:stats"    
 [4] "package:graphics"  "package:grDevices" "package:utils"    
 [7] "package:datasets"  "package:methods"   "Autoloads"        
[10] "package:base"   

因此"list(x = 42)"是全球环境的父级,统计信息是"list(x = 42)"的父级,依此类推。

答案 1 :(得分:7)

认为这是因为您没有定义f的任何参数,因此x所需的print(x)是如何查找的。

在正常使用中f()将在全局环境中查找x(如果未提供)(并且它不是,也不能像f那样不参数)。

with()内,会发生f所需的任何参数都将来自data参数。但由于您的f不接受任何参数,因此从不使用列表中的x。相反,R恢复到通常的行为,并在x环境中查找f,全球环境,当然它不在那里。与attach()的区别在于它明确地将包含x的对象添加到全局环境中的搜索路径。

如果你正确地编写了你的​​函数,遵循你传入函数中使用的任何和所有参数的咒语,那么一切都按照预期的方式工作:

> F <- function(x) print(x)
> with(list(x = 42), F(x))
[1] 42
> ls()
[1] "f" "F"

如果您已经有呼叫所需的列表或参数,可以考虑do.call()为您设置呼叫,而不是使用。例如:

> do.call(F, list(x = 42))
[1] 42

您仍然需要使用参数正确定义函数,因为f不起作用:

> do.call(f, list(x = 42))
Error in function ()  : unused argument(s) (x = 42)

答案 2 :(得分:3)

?with州的描述:

  

在从数据构造的环境中评估R表达式,   可能会修改原始数据。

这意味着该函数在数据存在的环境中运行,但环境不在搜索路径上。因此,在此环境中运行的任何函数都不会找到未传递给它们的数据(因为它们在自己的环境中运行),除非指示查看parent.frame。请考虑以下事项:

> f <- function() print(x)
> f()
Error in print(x) : object 'x' not found
> with(list(x=42),f())
Error in print(x) : object 'x' not found
> x <- 13
> f()
[1] 13
> with(list(x=42),f())
[1] 13
> f2 <- function(x) print(get("x",parent.frame()))
> f2()
[1] 13
> with(list(x=42),f2())
[1] 42

为了阐明通常如何使用with,数据被传递给with创建的环境中的函数,并且通常被调用的函数称为“全局”变量。 / p>

答案 3 :(得分:3)

传递参数列表对我有用:

f <- function(params) with(params, {
    print(x)
})
f(list(x=42))
# [1] 42

但你应该考虑明确的引用,例如:

f <- function(params) {
    print(params$x)
}

因为在开发阶段,有很多变量,你做的事情就是时间问题:

f <- function(params) with(params, {
    # many lines of code 
    print(x)
})
x <- 7
f(list(y=8))
# [1] 7 # wasn't in params but you got an answer