关于运算符:Ruby中的|| =(or-equals)是什么意思?

What does ||= (or-equals) mean in Ruby?

以下代码在Ruby中的含义是什么?

1
||=

它的语法是否有任何意义或原因?


a ||= b是条件赋值运算符。这意味着如果a未定义或假,则评估b并将a设置为结果。同样,如果a被定义并且评估为真实,则不评估b,并且不进行任何分配。例如:

1
2
3
4
5
6
7
a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0

foo = false # => false
foo ||= true # => true
foo ||= false # => true

令人困惑的是,它看起来与其他赋值运算符(例如+=)类似,但表现不同。

  • a += b转换为a = a + b
  • a ||= b大致翻译为a || a = b

它是a || a = b的近似简写。区别在于,当a未定义时,a || a = b将升高NameError,而a ||= ba设置为b。如果ab都是局部变量,那么这种区别就不重要了,但如果它们是类的getter / setter方法则很重要。

进一步阅读:

  • http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html


这个问题经常在Ruby邮件列表和Ruby博客上讨论过,现在甚至在Ruby邮件列表上都有线程,其唯一的目的是收集Ruby邮件列表上讨论这个问题的所有其他线程的链接。 。

这是一个:|| =(OR Equal)线程和页面的权威列表

如果您真的想知道发生了什么,请查看Ruby语言草案规范的第11.4.2.3节"缩写分配"。

作为第一个近似值,

1
a ||= b

相当于

1
a || a = b

并不等同于

1
a = a || b

但是,这只是第一次近似,特别是如果a未定义。语义也有所不同,具体取决于它是简单的变量赋值,方法赋值还是索引赋值:

1
2
3
a    ||= b
a.c  ||= b
a[c] ||= b

都被区别对待。


简洁而完整的答案

1
a ||= b

评估方式与以下每行相同

1
2
3
a || a = b
a ? a : a = b
if a then a else a = b end

-

另一方面,

1
a = a || b

评估方式与以下每行相同

1
2
a = a ? a : b
if a then a = a else a = b end

-

编辑:正如AJedi32在评论中指出的那样,这只适用于:1。a是一个已定义的变量。 2.评估一次和两次不会导致程序或系统状态的差异。


简而言之,a||=b表示:如果aundefined, nil or false,则将b指定给a。否则,保持a完好无损。


基本上,


x ||= y表示

如果x有任何值,请保持不变,否则不要更改该值
x设置为y


它意味着或等于。它检查左侧的值是否已定义,然后使用它。如果不是,请使用右侧的值。您可以在Rails中使用它来缓存模型中的实例变量。

一个快速的基于Rails的示例,我们创建一个函数来获取当前登录的用户:

1
2
3
4
5
6
7
class User > ActiveRecord::Base

  def current_user
    @current_user ||= User.find_by_id(session[:user_id])
  end

end

它检查是否设置了@current_user实例变量。如果是,它将返回它,从而保存数据库调用。如果没有设置,我们进行调用,然后将@current_user变量设置为该调用。这是一种非常简单的缓存技术,但是当您在应用程序中多次获取相同的实例变量时非常适合。


1
x ||= y

1
x || x = y

"如果x为false或未定义,则x指向y"


确切地说,a ||= b表示"如果a未定义或假(falsenil),则将a设置为b并评估为(即返回)b,否则评估为< X1>"。

其他人经常试图通过说a ||= b等于a || a = ba = a || b来说明这一点。这些等价物有助于理解概念,但要注意它们在所有条件下都不准确。请允许我解释一下:

  • a ||= ba || a = b

    a是未定义的局部变量时,这些语句的行为会有所不同。在这种情况下,a ||= ba设置为b(并评估为b),而a || a = b将提高NameError: undefined local variable or method 'a' for main:Object

  • a ||= ba = a || b

    通常假定这些语句的等效性,因为对于其他缩写赋值运算符(即+=-=*=/=%=**=&=),类似的等价性是正确的, |=^=<<=>>=)。但是,对于||=,当a=是对象上的方法且a是真实的时,这些语句的行为可能会有所不同。在这种情况下,a ||= b将不执行任何操作(除了计算a),而a = a || b将在a的接收器上调用a=(a)。正如其他人所指出的,当调用a=a具有副作用时,例如向哈希添加键,这可能会有所不同。

  • a ||= ba = b unless a

    这些陈述的行为仅在它们评估时a是真实的时候有所不同。在这种情况下,a = b unless a将评估为nil(尽管a仍未按预期设置),而a ||= b将评估为a

  • a ||= bdefined?(a) ? (a || a = b) : (a = b) ????

    仍然没有。当存在method_missing方法时,这些语句可能会有所不同,该方法返回a的真值。在这种情况下,a ||= b将评估为method_missing返回的任何值,而不是尝试设置a,而defined?(a) ? (a || a = b) : (a = b)a设置为b并计算为b

好的,好吧,a ||= b相当于什么?有没有办法在Ruby中表达这一点?

好吧,假设我没有忽略任何东西,我相信a ||= b在功能上等同于......(鼓)

1
2
3
4
begin
  a = nil if false
  a || a = b
end

坚持,稍等!这不是第一个带有noop的例子吗?嗯,不太好。还记得我之前说过,当a是一个未定义的局部变量时,a ||= b只是不等于a || a = b吗?好吧,a = nil if false确保a永远不会被定义,即使该行永远不会被执行。 Ruby中的局部变量是词法范围的。


假设a = 2b = 3

那么,a ||= b将导致a的值,即2

当评估某个值未导致falsenil时...这就是为什么ll不评估b的值。

现在假设a = nilb = 3

然后a ||= b将导致3,即b的值。

因为它首先尝试评估导致nil的值,所以它评估b的值。

ror应用程序中使用的最佳示例是:

1
2
3
4
5
6
7
#To get currently logged in iser
def current_user
  @current_user ||= User.find_by_id(session[:user_id])
end

# Make current_user available in templates as a helper
helper_method :current_user

其中,当且仅当@current_user未在之前初始化时才触发User.find_by_id(session[:user_id])


unless x
x = y
end

除非x有一个值(它不是nil或false),否则将其设置为y

相当于

x ||= y


1
a ||= b

相当于

1
a || a = b

并不是

1
a = a || b

因为您使用默认值定义哈希的情况(哈希将返回任何未定义键的默认值)

1
a = Hash.new(true) #Which is: {}

如果您使用:

1
a[10] ||= 10 #same as a[10] || a[10] = 10

a仍然是:

1
{}

但是当你这样写的时候:

1
a[10] = a[10] || 10

变为:

1
{10 => true}

因为你已经在key 10中分配了自己的值,默认为true,所以现在为键10定义了哈希,而不是从不首先执行赋值。


这就像懒惰的实例化。
如果已定义变量,则将采用该值而不是再次创建该值。


这是默认的赋值表示法

例如:x || = 1
这将检查x是否为零。如果x确实为零,则它将为其分配新值(在我们的示例中为1)

更明确:
如果x == nil
x = 1
结束


还请记住||=不是原子操作,因此它不是线程安全的。根据经验,不要将它用于类方法。


|| =是一个条件赋值运算符

1
  x ||= y

相当于

1
  x = x || y

或者

1
2
3
4
5
if defined?(x) and x
    x = x
else
    x = y
end

作为一种常见的误解,a ||= b不等于a = a || b,但它的行为类似于a || a = b

但这是一个棘手的案例。如果未定义a,则a || a = 42会引发NameError,而a ||= 42会返回42。所以,它们似乎不是等同的表达方式。


a || = b

表示'a'中是否存在任何值,并且您不想改变它继续使用该值,否则如果'a'没有任何值,则使用'b'的值。

简单的单词,如果左侧不为空,则指向现有值,否则指向右侧的值。


||=仅在left == nil(或未定义或false)时将值赋值为right。


1
2
b = 5
a ||= b

这意味着:

1
a = a || b

这将是

1
a = nil || 5

所以最后

1
a = 5

现在,如果再次调用它:

1
2
3
4
5
6
a ||= b
a = a || b
a = 5 || 5
a = 5

b = 6

现在,如果再次调用它:

1
2
3
4
a ||= b
a = a || b
a = 5 || 6
a = 5

如果观察到,b值将不会分配给aa仍然会5

它是一个Memoization模式,在Ruby中用于加速访问器。

1
2
3
def users
  @users ||= User.all
end

这基本上转化为:

1
@users = @users || User.all

因此,您第一次调用此方法时将调用数据库。

将来对此方法的调用只返回@users实例变量的值。


1
2
3
4
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a ||= 2
=> 1

因为a已设置为1

1
2
3
4
irb(main):003:0> a = nil
=> nil
irb(main):004:0> a ||= 2
=> 2

因为anil


||=称为条件赋值运算符。

它基本上用作=,但有一个例外,如果已经分配了一个变量,它将不执行任何操作。

第一个例子:

1
x ||= 10

第二个例子:

1
2
x = 20
x ||= 10

在第一个示例中,x现在等于10.但是,在第二个示例中,x已经定义为20.因此条件运算符无效。运行x ||= 10x仍为20。


a ||= ba = b if a.nil?a = b unless a相同

但是所有3个选项都显示相同的性能吗?用Ruby 2.5.1这个

1
2
3
4
5
6
7
8
9
10
11
12
1000000.times do
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
end

在我的电脑上需要0.099秒,而

1
2
3
4
5
6
7
8
9
10
11
12
1000000.times do
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
end

需要0.062秒。这几乎要快40%。

然后我们还有:

1
2
3
4
5
6
7
8
9
10
11
12
1000000.times do
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
end

这需要0.166秒。

并不是说这会对整体性能产生重大影响,但如果您确实需要最后一点优化,那么请考虑这个结果。
顺便说一句:a = 1 unless a对于新手来说更容易阅读,它是不言自明的。

注1:多次重复分配线的原因是为了减少测量时间的循环开销。

注2:如果我在每次分配之前a=nil nil,结果是相似的。