在R中如何使用“ <<-”(作用域分配)?


140

我刚读完R前言中有关范围定义的内容,并对<<-分配感到非常好奇。

手册显示了一个(非常有趣的)示例<<-,我认为我理解了。我仍然缺少的是什么时候有用的上下文。

因此,我很乐意从您那里读到一些示例(或示例的链接),这些示例说明何时使用<<-有趣/有用。使用它可能会有什么危险(看起来很容易松动),以及您可能想共享的任何提示。

Answers:


196

<<-与闭包一起维护状态最有用。这是我最近的一篇文章的一部分:

闭包是由另一个函数编写的函数。闭包之所以称为闭包,是因为它们封闭了父函数的环境,并且可以访问该函数中的所有变量和参数。这很有用,因为它允许我们具有两个级别的参数。一级参数(父级)控制函数的工作方式。另一个级别(孩子)负责这项工作。以下示例显示了如何使用此思想生成幂函数系列。父函数(power)创建实际完成工作的子函数(squarecube)。

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

在两个级别上管理变量的能力还使得可以通过允许函数在其父代环境中修改变量来维持函数调用之间的状态。管理不同级别变量的关键是双箭头分配运算符 <<-。与通常<-在当前级别上起作用的通常的单箭头分配()不同,双箭头运算符可以在父级别中修改变量。

这使得可以维护一个计数器,该计数器记录一个函数被调用的次数,如下例所示。每次new_counter运行时,它都会创建一个环境,i在此环境中初始化计数器,然后创建一个新函数。

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

新功能是一个闭合,其环境是闭合环境。运行闭包counter_one和时counter_two,每个计数器都会在其封闭环境中修改计数器,然后返回当前计数。

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1

4
嘿,这是Rosettacode上尚未解决的R任务(rosettacode.org/wiki/Accumulator_factory#R)好吧,这是……
Karsten W.

1
在一个父函数中是否需要包含多个闭包?我只是尝试了一个代码段,似乎只有最后一个关闭被执行了……
mckf111 '18

除“ <<-”符号以外,是否还有其他等号符号?
Genom

38

可以认为<<-等效于assign(如果inherits将该函数中的参数设置为TRUE)。的好处assign是,它允许您指定更多的参数(例如环境),所以我更喜欢使用assign<<-大多数情况下。

使用<<-assign(x, value, inherits=TRUE)表示“搜索提供的环境的封闭环境,直到遇到变量'x'。” 换句话说,它将继续按顺序遍历环境,直到找到具有该名称的变量,并将其分配给该变量为止。这可以在功能范围内,也可以在全局环境中。

为了了解这些功能的作用,您还需要了解R环境(例如使用search)。

在运行大型仿真时,我经常使用这些功能,并且我想保存中间结果。这使您可以在给定函数范围之外创建对象,或者apply循环。这非常有帮助,尤其是当您担心大循环意外结束(例如数据库断开连接)时,在这种情况下,您可能会丢失过程中的所有内容。这等效于在长时间运行的过程中将结果写到数据库或文件中,只是将结果存储在R环境中。

我的主要警告是:请小心,因为您现在正在使用全局变量,尤其是在使用时<<-。这意味着您可能会遇到以下情况:函数希望使用环境中的对象值,而您期望它使用的是作为参数提供的对象值。这是函数式编程要避免的主要事情之一(请参阅副作用)。通过将值分配给唯一的变量名(使用带有设定值或唯一参数的粘贴)来避免此问题,该变量名从未在函数中使用,而仅用于缓存,以防万一我以后需要恢复(或执行一些元操作) -中间结果分析)。


3
谢谢塔尔。我有一个博客,尽管我并没有真正使用它。我永远也无法完成发布,因为除非完美,否则我不愿发表任何东西,而我只是没有时间去做……
Shane 2010年

2
曾经有位智者对我说,保持完美并不重要-仅表现突出-您的身份,职位也会如此。另外-有时读者会通过注释来帮助改进文本(这就是我的博客所发生的事情)。我希望有一天您会重新考虑:)
Tal Galili 2010年

9

我使用的一个地方<<-是使用tcl / tk的简单GUI。一些初始示例具有此功能-因为您需要在局部变量和全局变量之间进行区分以确保状态充分。例如看

 library(tcltk)
 demo(tkdensity)

使用<<-。否则,我同意Marek的意见:)-Google搜索可以提供帮助。


有趣的是,我不知何故tkdensity在R 3.6.0中找不到。
NelsonGon


5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")

11
这是使用的好例子<<-。在这种情况下,for循环会更清晰。
hadley'4

4

关于这个问题,我想指出的是,在<<-for循环中(错误地)应用运算符时,运算符的行为会很奇怪(可能还有其他情况)。给出以下代码:

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

您可能希望函数将返回预期的总和6,但是它返回0,并mySum创建一个全局变量并将其赋值为3。循环不是新的作用域“级别”。取而代之的是,R似乎不在fortest函数之外,找不到mySum要分配给的变量,因此在循环中第一次创建一个并分配值1。在随后的迭代中,赋值中的RHS必须引用(未更改的)内部mySum变量,而LHS必须引用全局变量。因此,每次迭代都会将全局变量的值覆盖为该迭代的值i,因此在函数退出时其值为3。

希望这对某人有帮助-今天让我难过了几个小时!(顺便说一句,只需替换<<-<-,该功能将按预期工作)。


2
在您的示例中,local mySum从不增加,而仅global mySum。因此,在for循环的每次迭代中,全局mySum值都会获得0 + i。您可以使用跟随debug(fortest)
ClementWalter

它与for循环无关。您引用两个不同的范围。<-如果您只想更新函数内的局部变量,则只需在函数内的所有位置一致使用即可。
smci 2016年

或在所有@smci中使用<<-。虽然最好避免全局变量。
以示例方式学习统计数据(

3

编写引用方法时,<<-运算符对于引用类也可能很有用。例如:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.