Ruby的Object#const_get如何实际工作?

Hul*_*iax 11 ruby

我最近发现Ruby(2.2.1)有一些"有趣"的行为.

module Foo
  class Foo
  end
  class Bar
  end
end

Foo.const_get('Foo') #=> Foo::Foo
Foo.const_get('Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo') #=> Foo
Foo.const_get('Foo::Bar') #=> NameError: uninitialized constant Foo::Foo::Bar
Foo.const_get('Foo::Foo::Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo::Foo::Bar') #=> NameError: uninitialized constant Foo::Foo::Bar
Foo.const_get('Foo::Foo::Foo::Foo::Bar') #=> Foo::Bar
Foo.const_get('Foo::Foo::Foo') #=> Foo::Foo
Foo.const_get('Foo::Foo::Foo::Foo') #=> Foo
Foo.const_get('Foo::Foo::Foo::Foo::Foo') #=> Foo::Foo
Foo.const_get('Foo::Foo::Foo::Foo::Foo::Foo') #=> Foo
Run Code Online (Sandbox Code Playgroud)

这有点令人惊讶.我的理解是const_get首先在接收器的常量集合中查找常量,然后查看Object的常量.好的.那么为什么Foo#const_get上面的第四个失败而第三个失败呢?

我也很好奇为什么要Foo#const_get根据::Foo你添加的数量来调用模块和类之间的替换.

mat*_*att 16

文档说:

如果提供了命名空间的类名,则此方法将以递归方式查找常量名称.

所以Foo.const_get('Foo::Bar')基本上是一样的Foo.const_get('Foo').const_get('Bar').使用此解释,您的结果是有道理的.

你的第三个例子:

Foo.const_get('Foo::Foo')
Run Code Online (Sandbox Code Playgroud)

是相同的

Foo.const_get('Foo').const_get('Foo')
Run Code Online (Sandbox Code Playgroud)

第一个const_get查看顶层Foo(模块)中定义的常量,并查找嵌套类.所以整个事情有效地成为:

Foo::Foo.const_get('Foo')
Run Code Online (Sandbox Code Playgroud)

然后第二个调用查看类,首先查看任何包含的常量(找不到),然后查看它的祖先.Object是一个祖先,并将顶级Foo模块作为常量,因此找到并返回.

这也解释了添加额外::Foos 时的交替.交替发生const_get在查找嵌套类的顶级模块和查找继承链并查找顶级模块的嵌套类之间.

Foo.const_get('Foo::Bar')还可以解释引发异常的第四个例子.它相当于

Foo.const_get('Foo').const_get('Bar')
Run Code Online (Sandbox Code Playgroud)

第一部分,Foo.const_get('Foo')与上面的情况相同,评估为Foo::Foo,所以整个事情现在有效地变成:

Foo::Foo.const_get('Bar')
Run Code Online (Sandbox Code Playgroud)

现在嵌套Foo类不包含Bar常量,并且查找继承链也没有Object,因此结果是a NameError.