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

What are the differences between "=" and "<-" assignment operators?

提问人:csgillespie 提问时间:11/16/2009 最后编辑:user438383csgillespie 更新时间:1/14/2023 访问量:363049

问:

赋值运算符和 R 中的运算符有什么区别?=<-

我知道运算符略有不同,如本例所示

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

但这是唯一的区别吗?

赋值运算符 r-faq

评论

64赞 joran 12/13/2014
如上所述,该符号的起源来自旧的 APL 键盘,这些键盘上实际上只有一个键。<-<-

答:

27赞 Haim Evgi 11/16/2009 #1

从官方 R 文档:

运算符并分配到他们所处的环境中 被评估。运算符可以在任何地方使用,而 operator = 只允许在顶层(例如,在 在命令提示符下键入的完整表达式)或作为 带支撑的表达式列表中的子表达式。<-=<-

评论

12赞 Steve Pitchers 9/16/2014
我认为“顶级”是指语句级别,而不是表达式级别。因此,它本身就是一个声明;其中将是一个表达式,并且无效。需要明确的是,这与你是否处于全球环境中无关。x <- 42if (x <- 42) {}
1赞 Konrad Rudolph 3/2/2017
这:“运算符=只允许在顶层”是一个普遍存在的误解,是完全错误的。
0赞 Pavel Minaev 3/6/2017
这不是真的 - 例如,即使赋值不是一个完整的表达式,这也有效:1 + (x = 2)
1赞 Aaron left Stack Overflow 10/15/2019
为了澄清康拉德·鲁道夫(KonradRudolph)和帕维尔米纳耶夫(PavelMinaev)的评论,我认为说它是完全错误的太强烈了,但有一个例外,那就是当它“通过大括号或额外的一对括号与周围的逻辑结构隔离时”。
1赞 moodymudskipper 1/10/2020
或者在 , , ....function() x = 1repeat x = 1if (TRUE) x = 1
797赞 Richie Cotton 11/16/2009 #2

当您使用赋值运算符在函数调用中设置参数值时,它们之间的区别会更加明显。例如:

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 中,在 RStudio 中 +(在 macOS 下为 +),在 emacs+ESS 中为 +(下划线)。<-Ctrl=Alt-Option-Shift-


如果您更喜欢写入,但希望对公开发布的代码(例如在 CRAN 上)使用更常见的赋值符号,则可以使用包中的 tidy_* 函数之一自动替换为 。=<-formatR=<-

library(formatR)
tidy_source(text = "x=1:5", arrow = TRUE)
## x <- 1:5

“为什么会抛出错误,但不会?”这个问题的答案是“这要归结为解析器中包含的魔力”。R 的语法包含许多模棱两可的情况,必须以一种或另一种方式解决。分析器选择以不同的顺序解析表达式的位,具体取决于是否使用了 或。x <- y = 5x <- y <- 5=<-

若要了解所发生的情况,您需要知道赋值以静默方式返回已赋值。例如,您可以通过显式打印更清楚地看到这一点。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)

这是因为优先级低于 ,如 上所示?语法帮助页面。=<-

评论

12赞 Uwe 6/14/2016
帕特里克·伯恩斯 (Patrick Burns) 的《R Inferno》第 8.2.26 章也提到了这一点(不是我,而是推荐)
6赞 Konrad Rudolph 7/27/2018
我刚刚意识到你对如何解释的解释有点错误:实际上,R 将其解释为(它本身或多或少等同于 )。哎呀!x <- x = 5​`<-<-`(x, y = 5, value = 5)tmp <- x; x <- `<-<-`(tmp, y = 5, value = 5)
13赞 Konrad Rudolph 4/12/2019
...我刚刚意识到,这个答案的第一部分是不正确的,不幸的是,它非常具有误导性,因为它延续了一个常见的误解:你在函数调用中使用的方式不执行赋值,也不是赋值运算符。这是一个完全不同的解析 R 表达式,它恰好使用相同的字符。此外,您显示的代码不会在函数范围内“声明”。函数声明执行上述声明。函数调用没有(使用命名参数会变得有点复杂)。=x...
0赞 Clark Thomborson 11/24/2022
在我看来,函数调用参数中的“=”运算符具有与参数列表外部相同的语义。在这两种上下文中,它都将名称绑定到对象。在这两种上下文中,都有一个写入时复制语义,即对对象的“新”名称进行的任何更改都将导致对象被复制(data.table 的情况除外)。在这两种情况下,对于副本是“深”还是“浅”,都存在足够的语义混淆空间。但。。。R 中的语义就像英语中的语法一样,因为它是一种方便而不是形式。
1赞 Konrad Rudolph 1/12/2023
@ClarkThomborson 语义根本不同,因为在 R 中,赋值是通过对赋值函数的函数调用来执行的常规操作。但是,在参数列表中并非如此。在参数列表中,是一个任意分隔符标记,该标记在解析后不再存在。解析后,R 看到(基本上)。而对于 R 来说,看到 .确实,在这两种情况下也会发生名称绑定,但对于赋值运算符,它会在调用赋值运算符函数后发生。==f(x = 1)call("f", 1)x = 1call("=", "x", 1)
111赞 Nosredna 11/16/2009 #3

Google 的 R 风格指南通过禁止使用“=”进行分配来简化问题。不是一个糟糕的选择。

https://google.github.io/styleguide/Rguide.xml

R 手册详细介绍了所有 5 个赋值运算符。

http://stat.ethz.ch/R-manual/R-patched/library/base/html/assignOps.html

评论

14赞 Matt Dowle 6/8/2012
请注意,R 会考虑任何非 0。因此,如果您打算测试 if is less ,您可以编写不会发出警告或错误,并且看起来工作正常。不过,只有在的时候才会有。TRUEx-yif (x<-y)FALSEy=0
53赞 Fernando 10/9/2013
如果可以使用,为什么要伤害眼睛和手指?在99.99%的时间里是可以的。但有时你需要,这是一段不同的历史。<-==<<-
2赞 caiohamamura 10/31/2023
除了 0.01% 之外,它不起作用的只是糟糕的、不可读的、骇人听闻的快捷方式代码,适合任何不是 R 的人。这就像做bitshift做半除法只是为了看起来很聪明(但你不是)。我的观点是 R 应该弃用运算符。<-
38赞 Aaron left Stack Overflow 1/29/2011 #4

根据 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

50赞 Steve Pitchers 9/10/2014 #5

x = y = 5等价于 ,因为赋值运算符从右到左“分组”,这有效。意思是:将 5 赋值给 ,留下数字 5;然后将该 5 分配给 .x = (y = 5)yx

这与 不同,这是行不通的!含义:将 的值赋给 ,留下 的值;然后将 5 分配给,嗯......,到底是什么?(x = y) = 5yxy

混合使用不同种类的赋值运算符时,绑定比 更紧密。所以被解释为 ,这是有意义的情况。<-=x = y <- 5x = (y <- 5)

不幸的是,被解释为 ,这是行不通的!x <- y = 5(x <- y) = 5

有关优先级(绑定)和分组规则,请参阅 和。?Syntax?assignOps

评论

0赞 BroVic 6/5/2023
这是一个简明扼要的答案,一针见血!
8赞 Denis Rasulev 12/11/2016 #6

这也可以增加对这两个运算符之间差异的理解:

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

259赞 Konrad Rudolph 7/28/2018 #7

赋值运算符和 R 中的运算符有什么区别?=<-

如示例所示,并且运算符优先级略有不同(这决定了它们在同一表达式中混合时的计算顺序)。事实上, R 中的语法给出了以下运算符优先级表,从高到低:=<-

…
‘-> ->>’           rightwards assignment
‘<- <<-’           assignment (right to left)
‘=’                assignment (right to left)
…

但这是唯一的区别吗?

既然你问的是赋值运算符:是的,这是唯一的区别。但是,如果您不这么认为,那将是可以原谅的。甚至 ?assignOps 的 R 文档也声称存在更多差异:

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

我们不要说得太细:R 文档是错误的。这很容易说明:我们只需要找到一个运算符的反例,该运算符不是 (a) 在顶层,也不是 (b) 表达式列表(即 )。— 事不宜迟:={…; …}

x
# Error: object 'x' not found
sum((x = 1), 2)
# [1] 3
x
# [1] 1

显然,我们已经在上下文 (a) 和 (b) 之外使用了 ,执行了一项任务。那么,为什么核心 R 语言功能的文档几十年来一直是错误的呢?=

这是因为在 R 的语法中,符号有两个不同的含义,经常被混淆(甚至被专家混淆,包括在上面引用的文档中):=

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

那么,R 如何确定给定的用法是引用运算符还是命名参数传递呢?我看看。=

在任何一段一般形式的代码中......

‹function_name›(‹argname› = ‹value›, …)
‹function_name›(‹args›, ‹argname› = ‹value›, …)

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

if (‹var› = ‹value›) …
while (‹var› = ‹value›) …
for (‹var› = ‹value› in ‹value2›) …
for (‹var1› in ‹var2› = ‹value›) …

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

在任何其他上下文中,引用赋值运算符调用。特别是,只需在子表达式周围加上括号即可使上述任何 (a) 有效,并且 (b) 赋。例如,以下执行赋值:=

median((x = 1 : 10))

而且:

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

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

John Chambers 的原始解释(R 文档可能基于此)实际上正确地解释了这一点:

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


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

评论

5赞 moodymudskipper 1/10/2020
关于优先级和 R 文档中的错误,优先级实际上正好介于 和 之间,这在覆盖时会产生重要后果,否则几乎没有。?=<-?
2赞 Konrad Rudolph 1/10/2020
@Moody_Mudskipper这太奇怪了!您似乎是对的,但根据源代码main/gram.y),正确记录了 的优先级,并且低于 和 。?=<-
2赞 Clark Thomborson 11/24/2022
我喜欢你对 R 语义的解释......我会改写如下。“=”运算符重载。它的基本语义是将正式名称绑定到函数调用的 arglist 中的实际参数。在函数调用之外的大多数(但不是全部)上下文中,它具有与“<-”相同的语义:它将名称绑定到现有对象(或常量值),具有写入时复制语义,如果该名称当前未定义,则其副作用是定义此名称。在少数情况下,它必然会 stop() 来警告幼稚或粗心的用户,他们将它与“==”运算符混淆。
2赞 Konrad Rudolph 11/24/2022
@ClarkThomborson 我不同意将其中一种含义称为“基本”语义,因为这意味着不存在的层次结构。而且我认为调用重载运算符也是令人困惑的:R 中的术语“运算符”(至少在 R 4.0 之前!)具有特定的含义,指的是具有特殊语法规则的函数调用。当用于将名称绑定到函数调用参数列表中的参数名称时,这不是正在执行的操作。没有发生调用,因此在此上下文中只是一个语法标记(like ),而不是运算符。===;
2赞 Konrad Rudolph 11/24/2022
@ClarkThomborson 不管你是否觉得它有用,它就是这样命名参数的语法标记在解析阶段后完全消失,它根本不在解析树中表示,也不会被评估。赋值和函数调用中的名称绑定在 R 中发生根本不同(在其他语言中,在不同程度上),它不仅仅是“时间区别”,也不是由于运算符优先级。=
2赞 Diego 9/7/2021 #8

我不确定帕特里克·伯恩斯 (Patrick Burns) 的书 R inferno 是否在这里被引用,其中 8.2.26 = 不是 < 的同义词 - 帕特里克说:“当你想设置函数的参数时,你显然不想使用'<-'。该书可在 https://www.burns-stat.com/documents/books/the-r-inferno/

评论

1赞 Konrad Rudolph 9/7/2021
是的,已经提到过了。但问题是关于赋值运算符的,而你的摘录是关于传递参数的语法。应该明确(因为围绕这一点存在大量混淆)这不是赋值运算符。
-1赞 Chunhui Gu 1/14/2023 #9

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/

此外,将 about 视为一个无形返回值的函数。<-

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

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

评论

0赞 Konrad Rudolph 1/15/2023
不幸的是,科林·费伊(Colin Fay)的文章(现在是您的答案)重复了关于所谓的 和 之间差异的常见误解。因此,这种解释是不正确的。请看我的回答,彻底纠正这个有害的谎言。为了明确说明:您可以重写要使用的第一个代码,而不是在不更改其含义的情况下使用:。就像<-='是一个无形地返回值的函数一样。=<-=<-df <- data.frame(a = rnorm(10), (b = rnorm(10))),
0赞 Chunhui Gu 1/15/2023
@Konrad Rudolph R 在设计语言和代码解释时使用了一些规则/原则,以提高效率和可用性,这是其他语言所没有的。我相信大多数询问两者之间的区别并好奇为什么与其他流行的科学/数学语言(如 Python)相比有多个赋值运算符的人。以及我是否可以安全地只使用其他语言中的一种。此外,在技术上也是一个函数,因为您可以在代码中覆盖函数的含义。=<-R=((b = rnorm(10))b <- rnorm(10)(
0赞 Konrad Rudolph 1/15/2023
是的,这都是真的,但这与我的评论有什么关系?“为什么”的答案是:“纯粹是历史的”,而“我可以用吗”的答案是“是”。其他所有内容,尤其是您在某些情况下不能使用的声明,都是不正确的。是的,当然你可以覆盖,就像你可以覆盖一样,所以,是的,从技术上讲,你可以重新定义它们,使它们不再相同。但你肯定同意,这纯粹是一种分心。==(=<-