在 Ruby 中,在类<< 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 解释器在解析常量标记时直接使用该嵌套。修改嵌套的唯一方法是使用关键字classmodule,它只包括使用该关键字的模块和类:

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.newModule.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]并且我们正在寻找带有令牌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

它与范围有关。当您在class << self内时,范围与您在class Something内时不同。因此,在class << self内部实际上没有称为A_CONSTANT的常数。

在 Ruby 中,每个 Ruby 的常量都有自己的路径,从带有符号::的主(根)开始(默认,我们不需要声明这个符号)。class不应该被视为关键字(一种静态),但是一种方法(一种动态)负责创建一个指向该class objectclass objectclass name constant,并且所有Constants都是在没有路径(P::Q::...)的类中定义的,将自动被视为属于路径:: ClassName::A_CONSTANT创建的类。

GLOBAL = 1
class SuperExample
A_CONSTANT = "super constant" # <-- ::SuperExample::A_CONSTANT
end
puts ::GLOBAL # 1
puts ::SuperExample # SuperExample
puts ::SuperExample::A_CONSTANT # "super constant"

看起来子类中的常量路径与父类具有相同的级别

class SubExample < SuperExample
end
puts ::SubExample::A_CONSTANT # "super constant"

正如我注意到的,类块内的所有常量(不带::)都将在类路径下设置路径,因此当您获得它们时,要么使用显式常量路径,要么在常量所属的类下获得:

class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def another
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def yet_another
puts SubExample::A_CONSTANT # ::SubExample::A_CONSTANT
end
end

现在检查class << self

class SubExample < SuperExample
class << self
puts self # <Class:SubExample>
def print_constant_works_2
puts self::A_CONSTANT # declare explicitly constant path
end
def print_constant_fails
puts A_CONSTANT # not declare explicitly <-- Class:SubExample::A_CONSTANT
end
end
end

如您所见,class << self内部的类是不同的,因此方法print_constant_fails内部常量A_CONSTANT路径指向未定义任何常量A_CONSTANTClass:SubExample,因此uninitialized constant #<Class:SubExample>::A_CONSTANT引发错误。

同时print_constant_works_2将起作用,因为我们声明了显式常量路径,在这种情况下self实际上是SubExample(调用SubExample.print_constant_works_2)。

现在让我们尝试在print_constant_fails内部使用显式路径::A_CONSTANT

def print_constant_fails
puts ::A_CONSTANT
end

引发的误差是uninitialized constant A_CONSTANT::A_CONSTANT被认为是一个全局常数(main)。

最新更新