a ||= b
是一个条件赋值运算符。它的意思是:
如果 a 未定义或为假,则评估 b 并将 a 设置为结果。
否则(如果 a 已定义且计算结果为真),则 b 不会被计算,并且不会发生赋值。
例如:
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 ||= b
将 a
设置为 b
。如果 a
和 b
都是局部变量,则这种区别并不重要,但如果其中任何一个是类的 getter/setter 方法,这种区别就很重要。
进一步阅读:
http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html
这个问题在 Ruby 邮件列表和 Ruby 博客上已经被讨论得如此频繁,以至于现在 Ruby 邮件列表上甚至还有一些线程,其唯一目的是收集 Ruby 邮件列表上讨论这个问题的所有其他线程的链接.
这是一个:The definitive list of ||= (OR Equal) threads and pages
如果您真的想知道发生了什么,请查看 Ruby Language Draft Specification 的第 11.4.2.3 节“Abbreviated assignments”。
作为第一个近似值,
a ||= b
相当于
a || a = b
并且不等于
a = a || b
但是,这只是第一个近似值,尤其是在 a
未定义的情况下。语义也不同,具体取决于它是简单的变量赋值、方法赋值还是索引赋值:
a ||= b
a.c ||= b
a[c] ||= b
都被区别对待。
a = false; a ||= true
确实不按照您的回答所说的那样做“细微差别”。
||=
是什么,而是试图将某人指向另一个线程(这很讽刺,因为你试图结束这个追逐)。为什么不直接说它是什么?我相信它会为你和读者节省更多的工作。否决。
简洁而完整的答案
a ||= b
评估方式与以下每一行相同
a || a = b
a ? a : a = b
if a then a else a = b end
-
另一方面,
a = a || b
评估方式与以下每一行相同
a = a ? a : b
if a then a = a else a = b end
-
编辑:正如 AJedi32 在评论中指出的那样,这仅在以下情况下成立: 1. a 是已定义的变量。 2. 评估一次和两次不会导致程序或系统状态的差异。
a
为假/零/未定义,则会对其进行两次评估。 (但我不了解 Ruby,所以我不知道 lvalues 是否可以准确地“评估”......)
a
未定义,a || a = b
、a ? a : a = b
、if a then a else a = b end
和 if a then a = a else a = b end
将引发错误,而 a ||= b
和 a = a || b
不会。此外,当 a
为真时,a || a = b
、a ? a : a = b
、if a then a else a = b end
、a = a ? a : b
和 if a then a = a else a = b end
对 a
求值两次,而 a ||= b
和 a = a || b
则不然。
a
为真时,a || a = b
不会计算 a
两次。
the end state will be equivalent after the whole line has been evaluated
但这不一定是真的。如果 a
是一个方法怎么办?方法可能有副作用。例如,对于 public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5
,self.a ||= b
将返回 6,但 self.a ? self.a : self.a = b
将返回 7。
简而言之,a||=b
的意思是:如果 a
是 undefined, nil or false
,则将 b
分配给 a
。否则,保持 a
不变。
x ||= y
表示
如果 x
有任何值,不要理会它,不要更改值,否则将 x
设置为 y
x
的值是假的(nil
或 false
),x
仍然可以有一个值并且仍然可以进行分配。
它的意思是或等于。它检查左侧的值是否已定义,然后使用它。如果不是,请使用右侧的值。您可以在 Rails 中使用它来缓存模型中的实例变量。
一个基于 Rails 的快速示例,我们创建一个函数来获取当前登录的用户:
class User > ActiveRecord::Base
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
end
它检查是否设置了@current_user 实例变量。如果是,它将返回它,从而节省数据库调用。但是,如果未设置,我们会进行调用,然后将 @current_user 变量设置为该变量。这是一种非常简单的缓存技术,但非常适合在应用程序中多次获取相同的实例变量时使用。
undefined
上触发,而且在 false
和 nil
上触发,这可能与 current_user
无关,但尤其是 false
在其他情况下可能会出乎意料
x ||= y
是
x || x = y
“如果 x 为假或未定义,则 x 指向 y”
准确地说,a ||= b
表示“如果 a
未定义或为假(false
或 nil
),则将 a
设置为 b
并计算为(即返回)b
,否则计算为 { 2}”。
其他人经常试图通过说 a ||= b
等同于 a || a = b
或 a = a || b
来说明这一点。这些等价物有助于理解该概念,但请注意,它们并非在所有条件下都是准确的。请允许我解释一下:
一个 ||= b ⇔ 一个 || a = b?当 a 是未定义的局部变量时,这些语句的行为会有所不同。在这种情况下,a ||= b 会将 a 设置为 b(并计算为 b),而 a || a = b 将引发 NameError: undefined local variable or method 'a' for main:Object。
a ||= b ⇔ a = a ||乙?通常假设这些语句的等价性,因为对于其他缩写赋值运算符(即 +=、-=、*=、/=、%=、**=、&=、|=、^=、 <<= 和 >>=)。但是,对于 ||=,当 a= 是对象上的方法且 a 为真时,这些语句的行为可能会有所不同。在这种情况下,a ||= b 将什么都不做(除了评估为 a),而 a = a || b 将在 a 的接收器上调用 a=(a)。正如其他人指出的那样,当调用 a=a 有副作用时,这可能会有所不同,例如将键添加到哈希中。
||= b ⇔ a = b 除非 a??当 a 为真时,这些语句的行为仅在它们评估的内容上有所不同。在这种情况下,a = b 除非 a 将评估为 nil(尽管 a 仍不会像预期的那样被设置),而 a ||= b 将评估为 a。
a ||= b ⇔ 已定义?(a) ? (a || a = b) : (a = b)????仍然没有。当存在为 a 返回真值的 method_missing 方法时,这些语句可能会有所不同。在这种情况下, a ||= b 将评估任何method_missing返回,而不是尝试设置a,而定义?(a)? (a || a = b) : (a = b) 将 a 设置为 b 并计算为 b。
好的,好的,那么 a ||= b
等价于什么?有没有办法在 Ruby 中表达这一点?
好吧,假设我没有忽略任何东西,我相信 a ||= b
在功能上等同于...(drumroll)
begin
a = nil if false
a || a = b
end
坚持,稍等!这不就是第一个在它之前有 noop 的例子吗?嗯,不完全是。还记得我之前说过,当 a
是未定义的局部变量时,a ||= b
才不等同于 a || a = b
?好吧,a = nil if false
确保 a
永远不会未定义,即使该行从未执行过。 Ruby 中的局部变量是词法范围的。
(a=b unless a) or a
a
是一个方法,它将被调用两次而不是一次(如果它第一次返回一个真值)。例如,如果 a
需要很长时间才能返回或有副作用,这可能会导致行为不同。
b
to a
,rhs 是否仍然分配给 lhs,或者换句话说,lhs 是否仍然设置它对 rhs 的价值?
如果 X
没有值,它将被分配 Y
的值。否则,它将保留其原始值,在此示例中为 5:
irb(main):020:0> x = 5
=> 5
irb(main):021:0> y = 10
=> 10
irb(main):022:0> x ||= y
=> 5
# Now set x to nil.
irb(main):025:0> x = nil
=> nil
irb(main):026:0> x ||= y
=> 10
unless x x = y end
除非 x 有值(它不是 nil 或 false),否则将其设置为等于 y
相当于
x ||= y
假设 a = 2
和 b = 3
那么,a ||= b
将得到 a
的值,即 2
。
就像当 a 评估某个值时不会导致 false
或 nil
.. 这就是它 ll
不评估 b
的值的原因。
现在假设 a = nil
和 b = 3
。
然后 a ||= b
将得到 3
即 b
的值。
当它第一次尝试评估导致 nil
的值时,它评估了 b
的值。
ror 应用程序中使用的最佳示例是:
#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])
。
||= 是条件赋值运算符
x ||= y
相当于
x = x || y
或者
if defined?(x) and x
x = x
else
x = y
end
一个 ||= b
表示“a”中是否存在任何值并且您不想更改它并继续使用该值,否则如果“a”没有任何值,则使用“b”的值。
简单的话,如果左侧不为空,则指向现有值,否则指向右侧的值。
a ||= b
相当于
a || a = b
并不是
a = a || b
由于您使用默认值定义散列的情况(散列将返回任何未定义键的默认值)
a = Hash.new(true) #Which is: {}
如果您使用:
a[10] ||= 10 #same as a[10] || a[10] = 10
a 仍然是:
{}
但是当你这样写时:
a[10] = a[10] || 10
变成:
{10 => true}
因为您已经在键 10
处分配了自身的值,默认为 true,所以现在为键 10
定义了哈希,而不是从一开始就不执行分配。
这就像惰性实例化。如果变量已经定义,它将采用该值而不是再次创建该值。
另请记住,||=
不是原子操作,因此它不是线程安全的。根据经验,不要将它用于类方法。
||=
称为条件赋值运算符。
它基本上与 =
一样工作,但如果变量已被分配,它将什么也不做。
第一个例子:
x ||= 10
第二个例子:
x = 20
x ||= 10
在第一个示例中,x
现在等于 10。但是,在第二个示例中,x
已经定义为 20。因此条件运算符无效。运行 x ||= 10
后 x
仍为 20。
这是默认的赋值符号
例如: x ||= 1 这将检查 x 是否为 nil。如果 x 确实为 nil,它将为其分配新值(在我们的示例中为 1)
更明确:如果 x == nil x = 1 结束
nil
或 false
,不仅是 nil
b = 5
a ||= b
这转化为:
a = a || b
这将是
a = nil || 5
所以最后
a = 5
现在,如果您再次调用它:
a ||= b
a = a || b
a = 5 || 5
a = 5
b = 6
现在,如果您再次调用它:
a ||= b
a = a || b
a = 5 || 6
a = 5
如果您观察到,b
值将不会分配给 a
。 a
仍然有 5
。
它是一种在 Ruby 中用于加速访问器的记忆模式。
def users
@users ||= User.all
end
这基本上转化为:
@users = @users || User.all
因此,您将在第一次调用此方法时调用数据库。
以后对该方法的调用将只返回 @users
实例变量的值。
作为一种常见的误解,a ||= b
不等同于 a = a || b
,但它的行为类似于 a || a = b
。
但这里有一个棘手的案例。如果未定义 a
,则 a || a = 42
引发 NameError
,而 a ||= 42
返回 42
。所以,它们似乎不是等价的表达方式。
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a ||= 2
=> 1
因为 a
已设置为 1
irb(main):003:0> a = nil
=> nil
irb(main):004:0> a ||= 2
=> 2
因为 a
是 nil
这种 ruby-lang 语法。正确的答案是检查 ruby-lang 文档。所有其他解释都令人困惑。
谷歌
“ruby-lang 文档缩写作业”。
Ruby 语言文档
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
a ||= b
与 a = b if a.nil?
或 a = b unless a
相同
但是所有 3 个选项都显示相同的性能吗?使用 Ruby 2.5.1 这个
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 秒,而
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%。
然后我们还有:
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,结果是相似的。
h = Hash.new(0); h[1] ||= 2
。现在考虑两种可能的扩展h[1] = h[1] || 2
与h[1] || h[1] = 2
。两个表达式的计算结果都是0
,但第一个不必要地增加了散列的大小。也许这就是 Matz 选择让||=
表现得更像第二个扩展的原因。 (我基于另一个答案中链接的一个线程的示例。)a
未定义,a || a = b
会引发NameError
。a ||= b
没有,而是初始化a
并将其设置为b
。据我所知,这是两者之间的唯一区别。同样,我知道的a = a || b
和a ||= b
之间的唯一区别是,如果a=
是一个方法,那么无论a
返回什么,它都会被调用。此外,我知道的a = b unless a
和a ||= b
之间的唯一区别是,如果a
为真,则该语句的计算结果为nil
而不是a
。很多近似值,但没有什么完全等价的......