“=”和“<-”赋值运算符有什么区别?

945

在R中,赋值运算符=<-有何不同?

我知道这两个运算符略有不同,就像下面的例子所示:

x <- y <- 5
x = y = 5
x = y <- 5
x <- y = 5
# Error in (x <- y) = 5 : could not find function "<-<-"

但这是唯一的区别吗?


64
如此这里所述,"<-"符号的起源可以追溯到早期的APL键盘上,它们实际上只有一个"<-"键。 - joran
9个回答

796

当您在函数调用中使用赋值运算符来设置参数值时,赋值运算符的差异更加明显。例如:

median(x = 1:10)
x   
## Error: object 'x' not found

在这种情况下,x是在函数的范围内声明的,因此它不存在于用户工作区中。
median(x <- 1:10)
x    
## [1]  1  2  3  4  5  6  7  8  9 10

在这种情况下,x 在用户工作区内被声明,因此您可以在函数调用完成后使用它。

在 R 社区中,通常更倾向于使用 <- 进行赋值(除了在函数签名中),以确保与早期版本的 S-Plus 兼容。请注意,空格有助于澄清像这样的情况:

x<-3
# Does this mean assignment?
x <- 3
# Or less than?
x < -3

大多数 R IDE 都有键盘快捷键来方便地输入 <-。在 Architect 中使用 Ctrl + =,在 RStudio 中使用 Alt + -(macOS 下是 Option + -),在 emacs+ESS 中使用 Shift + -(下划线)。


如果你更喜欢写=而不是<-,但是想在公开发布的代码(例如在CRAN上)中使用更常见的赋值符号,则可以使用formatR包中的tidy_*函数自动将=替换为<-
library(formatR)
tidy_source(text = "x=1:5", arrow = TRUE)
## x <- 1:5

问题“为什么x <- y = 5会出错但是x <- y <- 5不会?”的答案是“这取决于解析器中包含的神奇内容”。R语言的语法包含许多模糊的情况,必须以某种方式解决。解析器根据使用的是=还是<-来选择以不同的顺序解析表达式的各个部分。
要理解发生了什么,您需要知道赋值会默默地返回被赋的值。例如,您可以通过显式打印来更清楚地看到这一点,如print(x <- 2 + 3)
其次,如果我们使用前缀符号表示赋值,那么就更清晰了。所以
x <- 5
`<-`(x, 5)  #same thing

y = 5
`=`(y, 5)   #also the same thing

解析器将x <- y <- 5解释为
`<-`(x, `<-`(y, 5))

我们可能会期望x <- y = 5是这样的

`<-`(x, `=`(y, 5))

但实际上它被解释为

`=`(`<-`(x, y), 5)

这是因为,如?Syntax页面所示,=的优先级低于<-


12
这也在Patrick Burns所著《The R Inferno》的第8.2.26章中提到过(推荐阅读,但这个作者并非我自己)。 - Uwe
6
我刚刚意识到你关于 x <- x = 5 如何被解释的说明稍有错误:实际上,R 将其解释为 ​`<-<-`(x, y = 5, value = 5) (这本身多少等价于 tmp <- x; x <- `<-<-`(tmp, y = 5, value = 5))。糟糕! - Konrad Rudolph
13
我刚刚意识到,这个答案的第一部分是错误的,不幸的是,它会持续误导人们认为 = 在函数调用中的使用 并不执行赋值,也不是一个赋值运算符。它是一个完全独立的 R 表达式,在这个表达式中仅仅是使用了相同的字符。此外,您展示的代码并没有在函数作用域中“声明” x函数声明 才能进行该声明。函数调用不会(使用命名的 ... 参数将会变得更加复杂)。 - Konrad Rudolph
在函数调用的参数中,"=" 运算符似乎具有与其在 arglist 之外相同的语义。在两种情况下,它都将名称绑定到对象。在两种情况下,都存在写时复制的语义,即对其“新”名称下的对象进行任何更改都将导致对象被复制(除了 data.table 的情况)。在这两种情况下,都存在大量的语义混淆空间,关于复制是“深层”还是“浅层”。但是...在 R 中的语义就像英语中的语法一样,是一种便利而不是形式上的要求。 - Clark Thomborson
1
@ClarkThomborson 语义上是根本不同的,因为在 R 中赋值是通过调用赋值函数的 函数调用 执行的常规操作。但是,在参数列表中这种情况却 不是 这样。在参数列表中,=是一个任意的分隔符标记,在解析后不再存在。在解析 f(x = 1) 后,R 看到(基本上)call("f", 1)。而对于 x = 1,R 看到的是 call("=", "x", 1)。确实,在两种情况下名称绑定也会发生,但是对于赋值运算符,它会在调用赋值运算符函数后发生。 - Konrad Rudolph
显示剩余4条评论

259
在R中,赋值运算符 =<- 的区别在哪里?如您的示例所示,它们具有略微不同的操作符优先级(确定它们在同一表达式中混合时的求值顺序)。实际上,R中的?Syntax给出以下操作符优先级表,从高到低:
…
‘-> ->>’           rightwards assignment
‘<- <<-’           assignment (right to left)
‘=’                assignment (right to left)
…
但这是唯一的区别吗?
既然你在询问“赋值运算符”,是的,这就是唯一的区别。但即使如此,你也可以原谅地认为有其他的区别。即使R文档中关于 ?assignOps 的说明声称存在更多的不同之处:

运算符<-可以用在任何地方, 而运算符=只允许在顶层(例如,在命令提示符下键入的完整表达式)或作为一个表达式列表中的子表达式之一。

让我们不要太苛求:R文档是错误的。这很容易证明:我们只需要找到一个反例来说明这个=运算符既不在顶层,也不是作为表达式列表中的子表达式之一(即{…; …})。 —无需多言:
x
# Error: object 'x' not found
sum((x = 1), 2)
# [1] 3
x
# [1] 1

显然我们在上下文(a)和(b)之外使用了=进行赋值。那么,为什么核心R语言特性的文档几十年来一直是错误的呢?

这是因为在R的语法中,符号=具有两个不同的含义,它们经常被混淆(甚至包括上面引用的专家在内的人都会如此):

  1. 第一个含义是赋值运算符。到目前为止,这就是我们所讨论的全部内容。
  2. 第二个含义不是运算符,而是语法标记,它表示函数调用中的命名参数传递。与=运算符不同,它在运行时不执行任何操作,仅仅改变表达式的解析方式。

那么,R如何确定所使用的=是指运算符还是指命名参数传递?让我们看看。

在任何形式的代码中...

<i>‹function_name›</i>(<i>‹argname›</i> <b>=</b> <i>‹value›</i>, …)
<i>‹function_name›</i>(<i>‹args›</i>, <i>‹argname›</i> <b>=</b> <i>‹value›</i>, …)

...=是定义命名参数传递的标记:它不是赋值运算符。此外,在某些语法上下文中完全禁止使用=

if (<i>‹var›</i> <b>=</b> <i>‹value›</i>) …
while (<i>‹var›</i> <b>=</b> <i>‹value›</i>) …
for (<i>‹var›</i> <b>=</b> <i>‹value›</i> in <i>‹value2›</i>) …
for (<i>‹var1›</i> in <i>‹var2›</i> <b>=</b> <i>‹value›</i>) …

任何一个都将引发错误“在‹bla›中出现意外的'='”。

在任何其他上下文中,=指的是赋值运算符调用。特别地,仅仅在子表达式周围放置括号就可以使上述任何一个(a)有效,并且(b)是一个赋值。例如,以下代码执行了赋值:

median((x = 1 : 10))

但是也:

if (! (nf = length(from))) return()

现在你可能会反对这样的代码是极其糟糕的(你可能是对的)。但我从base::file.copy函数中取出了这段代码(将<-替换为=)—— 这是许多核心R代码库中普遍存在的模式。

John Chambers的原始解释,可能是R文档基于此的,实际上正确地解释了这个问题:

[= 赋值语句] 只允许在两个语法结构中使用:在顶层(作为完整程序或用户键入的表达式)和当它与周围逻辑结构隔离时,通过大括号或额外的一对圆括号。


总之,默认情况下,运算符<-=执行相同的操作。但是,它们中的任何一个都可以单独被覆盖以更改它的行为。相比之下,<-->(从左到右赋值)虽然在语法上不同,但始终调用相同的函数。覆盖一个也会覆盖另一个。了解这一点很少实用但它可以用于一些有趣的小把戏


5
关于R文档中的优先级和错误,?的优先级实际上位于=<-之间,在覆盖?时具有重要影响,但在其他情况下几乎没有影响。 - moodymudskipper
2
@Moody_Mudskipper 这太奇怪了!你似乎是对的,但根据源代码main/gram.y),?的优先级被正确记录,并且低于=<- - Konrad Rudolph
2
我喜欢你对R语言语义的解释... 我会重新表述一下。"="运算符是重载的。它的基本语义是将形式名称绑定到函数调用的参数列表中的实际参数。在大多数(但不是全部!)函数调用之外的上下文中,它具有与"<-"相同的语义:将名称绑定到现有对象(或常量值),并采用写时复制语义,同时定义此名称(如果当前未定义)。在少数情况下,它被绑定到stop()以警告天真或粗心的用户,他们将其与"=="运算符混淆。 - Clark Thomborson
2
@ClarkThomborson 我不同意将其中一个含义称为“基本”语义,因为这暗示了一个不存在的层次结构。而且我认为将=称为重载运算符也很令人困惑:在R中,“运算符”一词(至少在R 4.0之前)具有特定的含义,指的是具有特殊语法规则的函数调用。当用于将名称绑定到函数调用参数列表中的参数名称时,=并不是在执行调用操作。在这种情况下,=只是一个语法标记(如;),而不是运算符。 - Konrad Rudolph
2
@ClarkThomborson 不管你是否认为它有用,这就是它的本质:命名参数的语法=标记在解析阶段后完全消失,它在解析树中根本没有表示,也不会被评估。在R(以及其他语言中)中,赋值和函数调用中的名称绑定基本上是以不同的方式发生的,它不仅仅是“时间上的区别”,也不是由于运算符优先级引起的。 - Konrad Rudolph
显示剩余5条评论

112

13
请注意,任何非0值在R中都被视为“TRUE”。因此,如果您打算测试x是否小于-y,则可以编写if (x<-y),它不会发出警告或错误,并且看起来运行良好。但是仅当y=0时,它才是“FALSE”。 - Matt Dowle
53
如果你可以使用=,为什么还要用<-来伤害你的眼睛和手指呢?99.99%的情况下,使用=就足够了。不过有时你需要使用<<-,那就是另一回事了。 - Fernando
2
除了0.01%的情况下它无法运行外,这种写法只是糟糕得无法阅读,是一种不专业的捷径代码,只有R语言专家才能看懂。这就好比为了显得聪明而使用位移操作来进行一半的除法,但实际上并不聪明。我认为R语言应该废弃<-操作符。 - undefined

50
x = y = 5相当于x = (y = 5),因为赋值运算符是从右往左“分组”的,这是可行的。意思是:将5分配给y,留下数字5;然后将该5分配给x
这不同于(x = y) = 5,这种情况无法实现!意思是:将y的值赋给x,保留y的值;然后将5分配给...什么?
当您混合不同类型的赋值运算符时,<-=更紧密。因此,x = y <- 5被解释为x = (y <- 5),这是有意义的情况。
不幸的是,x <- y = 5被解释为(x <- y) = 5,这是不可行的情况!
请参见?Syntax?assignOps获取优先级(绑定)和分组规则。

这是一个简洁明了的回答,一针见血! - BroVic
这是一个简洁明了的回答,一针见血! - undefined

38

根据John Chambers的说法,运算符=只允许在"顶层"使用,这意味着它在控制结构(如if)中是不允许使用的,因此以下编程错误是非法的。

> if(x = 0) 1 else x
Error: syntax error
他写道,“在控制表达式中禁止使用新的赋值形式 [=] 可以避免编程错误(如上面的例子),这些错误比其他S赋值操作符更容易发生。”
如果它“与周围的逻辑结构隔离开来,通过大括号或额外的一对括号”,你就可以做到这一点,所以if ((x = 0)) 1 else x将起作用。
请参见http://developer.r-project.org/equalAssign.html

27

来自R官方文档:

运算符 <-= 将赋值给它们所在的环境。 运算符 <- 可以在任何地方使用,而运算符 = 仅允许在顶层使用(例如,在命令提示符处键入的完整表达式)或作为表达式列表中的子表达式之一。


12
我认为 "top level" 意味着在语句级别,而不是表达式级别。因此,单独的 x <- 42 是一条语句;在 if (x <- 42) {} 中它将成为一个表达式,并且是无效的。需要明确的是,这与您是否在全局环境中无关。 - Steve Pitchers
1
“the operator = is only allowed at the top level” 这句话是一个被广泛误解的说法,完全是错误的。 - Konrad Rudolph
这并不是真的——例如,尽管赋值不是一个完整的表达式,但下面的代码可以正常工作:1 + (x = 2) - Pavel Minaev
1
为了澄清KonradRudolph和PavelMinaev的评论,我认为说它完全错误有些过分,但是有一个例外情况,即当它“被大括号或额外的一对括号与周围的逻辑结构隔离开来”时。 - Aaron left Stack Overflow
1
function() x = 1repeat x = 1if (TRUE) x = 1 中... - moodymudskipper

8
这也可能有助于理解这两个运算符之间的区别:

df <- data.frame(
      a = rnorm(10),
      b <- rnorm(10)
)

对于第一个元素,R已经分配了值和适当的名称,而第二个元素的名称看起来有点奇怪。

str(df)
# 'data.frame': 10 obs. of  2 variables:
#  $ a             : num  0.6393 1.125 -1.2514 0.0729 -1.3292 ...
#  $ b....rnorm.10.: num  0.2485 0.0391 -1.6532 -0.3366 1.1951 ...

R版本 3.3.2 (2016-10-31);macOS Sierra 10.12.1


2

我不确定是否在此处引用了Patrick Burns的书《R地狱》中的内容,其中在8.2.26 =不是<-的同义词中,Patrick指出:“当你想设置函数参数时,显然不想使用'<-'。”该书可在https://www.burns-stat.com/documents/books/the-r-inferno/获取。


1
是的,已经提到过了。但问题是关于赋值运算符,而你的摘录涉及传递参数的语法。需要明确(因为这一点存在相当大的混淆),这不是赋值运算符。 - Konrad Rudolph

-1

在过去的R版本或者R的前身语言S语言中,<-=之间存在一些差异。但是目前看来,仅使用=就像其他现代语言(如Python、Java)一样不会引起任何问题。当您同时传递一个值到某些参数并创建全局变量时,使用<-可以实现一些更多的功能,但它可能会产生奇怪/不想要的行为,例如:

df <- data.frame(
      a = rnorm(10),
      b <- rnorm(10)
)

str(df)
# 'data.frame': 10 obs. of  2 variables:
#  $ a             : num  0.6393 1.125 -1.2514 0.0729 -1.3292 ...
#  $ b....rnorm.10.: num  0.2485 0.0391 -1.6532 -0.3366 1.1951 ...

强烈推荐!尝试阅读这篇文章,它是最好的一篇文章,试图解释这两者之间的区别: 查看 https://colinfay.me/r-assignment/

此外,将<-视为一个函数,该函数会隐式返回一个值。

a <- 2
(a <- 2)
#> [1] 2

请参见:https://adv-r.hadley.nz/functions.html


不幸的是,Colin Fay的文章(现在包括你的回答)重复了关于=<-之间所谓差异的常见误解。因此,这个解释是错误的。请参见我的回答,对这个有害的谬论进行详尽的纠正。明确地说:您可以将第一段代码重写为使用=而不是<-,而不改变其含义:df <- data.frame(a = rnorm(10), (b = rnorm(10)))。就像<-一样,=也是一个返回值的函数。 - Konrad Rudolph
@Konrad Rudolph在设计R语言和代码解释的效率和可用性方面使用了一些规则/原则,这些规则/原则在其他语言中并不常见。我相信大多数人询问=<-之间的区别是因为他们好奇为什么R相比于其他流行的科学/数学语言(如Python)有多个赋值运算符。此外,(在R中也是一个函数,因此从技术上讲,(b = rnorm(10))b <- rnorm(10)不同,因为您可以在代码中覆盖(函数的含义。 - Chunhui Gu
是的,这一切都是真的,但这与我的评论有什么关系呢? “为什么”的答案是:“纯粹是历史原因”,而“我能否只使用=”的答案是“是”。其他所有内容,特别是您声称在某些情况下无法使用=的说法,都是不正确的。是的,当然可以重写,就像可以重写=<-一样,因此,从技术上讲,您可以重新定义它们,使它们不再相同。但是您肯定会同意,这是一个纯粹的干扰。 - Konrad Rudolph

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