首页 > 解决方案 > 在 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

解决方案


您遇到了一个常见的 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.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.


推荐阅读