【发布时间】:2017-10-06 01:57:38
【问题描述】:
我最近阅读了一个blog post about Ruby's behaviours with regards to a local variable shadowing a method(与a block variable shadowing a method local variable 不同,在this StackOverflow thread 中也有讨论),我发现了一些我不太了解的行为。
Ruby's documentation says that:
[V] 变量名和方法名几乎相同。如果您没有指定这些模棱两可的名称之一,ruby 将假定您希望调用一个方法。一旦您指定了名称,ruby 将假定您希望引用一个局部变量。
所以,给定以下示例类
# person.rb
class Person
attr_accessor :name
def initialize(name = nil)
@name = name
end
def say_name
if name.nil?
name = "Unknown"
end
puts "My name is #{name.inspect}"
end
end
鉴于我现在通过阅读上述链接中的信息了解到的情况,我预计会出现以下情况:
-
name.nil?语句仍将引用attr_accessor提供的name实例方法 - 当 Ruby 解析器在
#say_name方法中看到name = "Unknown"赋值行时,它会认为任何对name的引用在之后 用于引用局部变量 - 因此,即使
Person在初始化时分配了name,#say_name方法最后一行中引用的name也将是nil
它看起来像这可以在irb 控制台中确认:
irb(main):001:0> require "./person.rb"
true
# `name.nil?` using instance method fails,
# `name` local variable not assigned
irb(main):002:0> Person.new("Paul").say_name
My name is nil
nil
# `name.nil?` using instance method succeeds
# as no name given on initialisation,
# `name` local variable gets assigned
irb(main):003:0> Person.new.say_name
My name is "Unknown"
nil
但是,如果我进行一些内联调试并使用 Pry 尝试跟踪 name 的引用如何变化,我会得到以下信息:
irb(main):002:0> Person.new("Paul").say_name
From: /Users/paul/person.rb @ line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[1] pry(#<Person>)> next
"Paul"
好的,这是有道理的,因为我假设 name 指的是实例方法。那么,让我们直接检查name的值...
From: /Users/paul/person.rb @ line 14 Person#say_name:
10: def say_name
11: binding.pry
12:
13: p name
=> 14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[2] pry(#<Person>)> name
nil
呃……这在当时是出乎意料的。我目前正在查看赋值行上方对name 的引用,所以我认为它仍然会引用实例方法而不是局部变量,所以现在我很困惑......我猜想@987654346 @assignment 会运行,然后...?
[3] pry(#<Person>)> exit
My name is nil
nil
不,返回值和以前一样。那么,这里发生了什么?
- 我对@987654348@ 引用
name实例方法的假设有误吗?它引用的是什么? - 这一切是否与处于 Pry 环境中有关?
- 我还错过了什么?
供参考:
➜ [ruby]$ ruby -v
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
编辑
- 此问题中的示例代码旨在说明(我认为)我所看到的意外行为,而不是以任何方式说明实际好的代码。
- 我知道,通过将局部变量重命名为其他名称,可以轻松避免这种阴影问题。
- 即使有阴影,我知道仍然可以通过专门调用方法来避免该问题,而不是使用
self.name或name()引用局部变量。
进一步解决这个问题,我开始认为这可能是 Pry 环境的一个问题。运行时Person.new("Paul").say_name:
From: /Users/paul/person.rb @ line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
此时,p 语句还没有运行,那么让我们看看 Pry 说的 name 的值是什么:
[1] pry(#<Person>)> name
nil
这是出乎意料的,因为 Ruby 的文档说由于尚未进行分配,因此应该调用方法调用。现在让p 语句运行...
[2] pry(#<Person>)> next
"Paul"
...返回name方法的值,这是预期的。
那么,Pry 在这里看到了什么?它是否以某种方式修改范围?为什么当 Pry 运行 name 时,它给出的返回值与 Ruby 本身运行 name 时不同?
【问题讨论】:
-
我只想说一个显而易见的问题:使用不同的变量名可以轻松避免整个麻烦,例如
display_name = name || "Unknown". -
是的,正确的。问题中的代码是按照严格说明我所看到的问题的方式编写的。