【问题标题】:Confusing behaviour of const_get in Ruby?Ruby 中 const_get 的令人困惑的行为?
【发布时间】:2014-11-11 03:45:50
【问题描述】:

根据文档mod.const_get(sym)“返回mod中命名常量的值。”

我也知道const_get默认会查找receiver的继承链。所以以下工作:

class A; HELLO = :hello; end
class B < A; end
B.const_get(:HELLO) #=> :hello

我也知道 Ruby 中的类是 Object 的子类,因此即使接收者是普通类,您也可以使用 const_get 查找“全局”常量:

class C; end
C.const_get(:Array) #=> Array

但是,这就是我感到困惑的地方——模块不继承Object。那么为什么我仍然可以使用const_get 从模块中查找“全局”常量?为什么以下工作?

module M; end
M.const_get(:Array) #=> Array

如果文档正确 - const_get 只需查找在接收器或其超类下定义的常量。但是在上面的代码中,Object 不是M 的超类,那么为什么可以查到Array 呢?

谢谢

【问题讨论】:

  • 请注意,这与:: 的行为不符。 SomeModule::SomeGlobalConstant 会导致错误,而SomeModule.const_get(:SomeGlobalConstant) 会起作用。

标签: ruby


【解决方案1】:

你是正确的混淆...该文档没有说明 Ruby 在Modules 中查找常量的特殊情况并已修改to state this explicitly。如果在正常层次结构中没有找到常量,Ruby 会从Object 重新开始查找,found in the source 也可以。

不断查找本身可能有点混乱。举个例子:

module M
  Foo = :bar
  module N
    # Accessing Foo here is fine:
    p Foo # => bar
  end
end
module M::N
  # Accessing Foo here isn't
  p Foo  # => uninitialized constant M::N::Foo
end
p M::N.const_get :Foo  # => uninitialized constant M::N::Foo

不过,在这两个地方,访问像 Array 这样的 Object 级别常量是可以的(感谢上帝!)。发生的事情是 Ruby 维护一个“打开的模块定义”列表。如果常量有明确的范围,比如LookHereOnly::Foo,那么only LookHereOnly 及其包含的模块将被搜索。如果没有指定范围(如上例中的Foo),Ruby 将查看打开的模块定义以找到常量FooM::N,然后是M,最后是Object。最顶层打开的模块定义总是Object

所以M::N.const_get :Foo 相当于在打开的类只有M::NObject 时访问Foo,就像我的示例的最后一部分一样。

我希望我做对了,因为我自己仍然对不断的查找感到困惑:-)

【讨论】:

  • 知道为什么会这样吗?在什么情况下会有用?
  • @sepp2k:不确定它是否有用,但我尝试解释我认为它背后的逻辑。
  • 这不直观,但 M::N 问题是由于词法范围。如果你这样做module M; module N; end; end,M 的内容在 N 的词法范围内(由于嵌套,M 从 N 中可见)。对于module M::N; end,M 不可见,因为包含范围是顶级的。就我个人而言,我尽量避免以 M::N 形式定义类和模块 - 也因为如果 M 尚不存在,这是一个错误。
  • 有趣的实验……试试 eval("M::N"),JRuby 也可以用这种方法做一些有趣的事情。 -- 是的,它是 eval 和 EVIL
【解决方案2】:

我想出了以下脚本来加载名称间隔的常量:

def load_constant(name)
  parts = name.split('::')
  klass = Module.const_get(parts.shift)
  klass = klass.const_get(parts.shift) until parts.empty?
  klass
end

【讨论】:

    【解决方案3】:

    只要我们不检查错误,您就可以:

    def load_constant(name)
        name.split('::').inject(Module) do |mod_path, mod_to_find|
          mod_path.const_get(mod_to_find)
        end
    end
    

    【讨论】:

      猜你喜欢
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多