Ruby单例方法调用C扩展父类常量触发NameError问题咨询
问题核心原因
这个差异本质是Ruby两套常量查找规则导致的:无修饰常量的查找同时依赖定义时的词法作用域和运行时的self类型,只有显式指定接收者的常量查找才会直接遍历接收者的普通继承链。
首先明确前提:你通过C扩展定义的BAR是存储在FooC类的常量表中,Foo本身没有定义BAR,仅通过继承和FooC关联。
不同场景的查找逻辑拆解
1. 实例方法、def self.puts_bar定义的类方法调用BAR正常的原因
这两类方法定义时的词法作用域都是Foo类本身,无修饰常量查找会按以下流程执行:
- 第一步:遍历词法作用域嵌套的所有模块的自有常量,未找到
BAR - 第二步:遍历当前
self的普通继承链- 实例方法运行时
self是Foo的实例,继承链为Foo → FooC → Object → ...,命中FooC::BAR def self.puts_bar定义的类方法运行时self是Foo类本身,继承链同样是Foo → FooC → ...,也能命中FooC::BAR
- 实例方法运行时
2. class << self块内的方法调用BAR报错的原因
当你打开class << self块时,实际操作的是Foo的单例类(eigenclass),此时定义的方法的词法作用域嵌套链变为[Foo的单例类, Foo],无修饰常量查找流程如下:
- 第一步:遍历词法作用域嵌套模块的自有常量:
Foo的单例类没有BAR,Foo本身也没有定义BAR,查找失败 - 第二步:遍历当前
self的类的继承链:运行时self是Foo类本身,类对象的所属类是它的单例类,所以会遍历单例类的继承链:Foo的单例类 → FooC的单例类 → Object的单例类 → ... → Class → Module → ...,整个链上的模块都没有存储BAR(BAR存储在FooC普通类的常量表,而非FooC的单例类的常量表),最终查找失败抛出NameError
三个特殊现象的解释
- 调用时写
Foo::BAR或self::BAR不报错:显式指定了常量查找的接收者,此时查找规则会直接遍历接收者的普通继承链,Foo的继承链包含FooC,所以能找到FooC::BAR - 相同常量调用写在
Foo的实例方法中无错误:实例方法的词法作用域是Foo,第二步查找走实例所属类的普通继承链,可命中FooC::BAR def self.puts_bar写法定义的类方法调用无错误:这种方式定义类方法时,方法的词法作用域仍然是Foo,没有进入单例类的词法上下文,第二步走的是Foo的普通继承链,可正常找到常量
内容的提问来源于stack exchange,提问作者jjg
相关产品推荐
相关产品推荐

