在Ruby中,在class<<self中定义的方法中,为什么不能在没有self的情况下访问超类上定义的常量?
我试图更好地理解 Ruby 单例和类继承。我到处都读到
def self.method_name; end`
相当于
class << self
def method_name; end
end
但如果那是真的,那么我希望print_constant_fails能工作,但事实并非如此。这里发生了什么?
class SuperExample
A_CONSTANT = "super example constant"
end
class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT
end
class << self
def print_constant_works_2
puts self::A_CONSTANT
end
def print_constant_fails
puts A_CONSTANT
end
end
end
pry(main)> SubExample.print_constant_works_1
super example constant
pry(main)> SubExample.print_constant_works_2
super example constant
pry(main)> SubExample.print_constant_fails
NameError: uninitialized constant #<Class:SubExample>::A_CONSTANT
from (pry):13:in `print_constant_fails'
回答
您遇到了一个常见的 Ruby 问题 - 持续查找。
常量查找中最重要的概念是Module.nesting(与方法查找中的主要起点不同self)。此方法为您提供当前模块嵌套,Ruby 解释器在解析常量标记时直接使用它。修改嵌套的唯一方法是使用关键字class,module并且它只包含您使用该关键字的模块和类:
class A
Module.nesting #=> [A]
class B
Module.nesting #=> [A::B, A]
end
end
class A::B
Module.nesting #=> [A::B] sic! no A
end
在元编程中,可以使用Class.newor动态定义模块或类Module.new- 这不会影响嵌套并且是错误的一个非常常见的原因(啊,也值得一提 - 在 Module.nesting 的第一个模块上定义了常量):
module A
B = Class.new do
VALUE = 1
end
C = Class.new do
VALUE = 2
end
end
A::B::VALUE #=> uninitialized constant A::B::VALUE
A::VALUE #=> 2
上面的代码将生成两个警告:一个是常量 A::VALUE 的双重初始化,第二个是重新分配常量。
如果它看起来像“我永远不会那样做” - 这也适用于RSpec.describe其中定义的所有常量(内部调用 Class.new),所以如果你在 rspec 测试中定义一个常量,它们肯定是全局的(除非你明确说明要在其中定义的模块self::)
现在让我们回到你的代码:
class SubExample < SuperExample
puts Module.nesting.inspect #=> [SubExample]
class << self
puts Module.nesting.inspect #=> [#<Class:SubExample>, SubExample]
end
end
解析常量时,解释器首先遍历所有模块Module.nesting并在该模块中搜索该常量。因此,如果嵌套是[A::B, A]并且我们正在寻找带有 token 的常量C,解释器将A::B::C首先寻找,然后寻找A::C。
但是,在您的示例中,这在两种情况下都会失败:)。然后解释器开始在 Module.nesting 中搜索第一个(也是唯一的第一个)模块的祖先。SubrExample.singleton_class.ancestors给你:
[
#<Class:SubExample>,
#<Class:SuperExample>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module,
Object,
Kernel,
BasicObject
]
正如您所看到的 - 没有SuperExample模块,只有它的单例类 - 这就是为什么在class << self( print_constant_fails) 中的常量查找失败。
他们的祖先Subclass是:
[
SubExample,
SuperExample,
Object,
Kernel,
BasicObject
]
我们在SuperExample那里,所以解释器将设法SuperExample::A_CONSTANT在这个嵌套中找到。
我们只剩下print_constant_works_2. 这是一个单例类的实例方法,所以self在这个方法中只是SubExample. 因此,我们正在寻找SubExample::A_CONSTANT- 常量查找首先搜索SubExample,如果失败,则搜索其所有祖先,包括SuperExample.