Ruby class variable另一奇怪现象的可能的解释
2013年4月24日 18:00
Ruby的class variable由于其奇怪的特性,一直是Ruby中包含争议的部分,大部分Rubist表示不使用class variable,最早我看到的文章是这篇写于2007年的帖子,里面的
1 2 3 4 5 | @@avar = 1 class A @@avar = "hello" end puts @@avar # => hello |
成为了一个经典案例,至于其解释就是Ruby的class variable不属于owner类本身,而属于它的继承结构。@@avar实际定义在main所在类Object中,A继承自Object,因此也继承了@@avar这个class variable。
而昨天我又发现了另一个奇怪的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class A @@a = 1 def f @@a end def f=(v) @@a = v end end A . new .f #=> 1 A . new .f = 2 A . new .f #=> 2 class A def self .f1 @@a end class << self def f2 @@a end end end A .f1 #=> 2 A .f2 #=> 2 # 直到这里,所有内容都可以理解 class << A def f3 @@a end end A .f3 #=> warning: class variable access from toplevel #=> NameError: uninitialized class variable @@a in Object |
这段代码的问题在于
1 | class << A ; ... ; end |
和
1 | class A ; class << self ; ... ; end ; end |
这两段代码本该是没有区别的,但是用后者访问的到A的class variable @@a而用前者却访问不到
当晚我把这段代码在Ruby Tuesday上提出过,也没人能回答我。回去以后仔细想想,可能觉得class variable对于gateway scope的敏感程度和其它两种变量类型local variable和instance variable不一样,在Ruby中,local variable不能穿越gateway scope存在,比如
1 2 3 4 5 6 7 8 | a = 1 class A b = 2 def f local_variables end end A . new .f #=> [] |
无论是a还是b,都没能进入到A的instance method f的定义中。而instance variable虽然也不能穿越gateway scope,但是等到gateway scope相同的时候却可以恢复出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class A def self .f1 @a = 1 end def f2 @a = 2 end def self .f3 @a end def f4 @a end end A .f1 A .f3 #=> 1 a = A . new a.f2 a.f4 #=> 2 |
但是class variable和它们恐怕都不一致,它是可以穿越gateway scope而存在的,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class A @@a = 1 def f @@a end def f=(v) @@a = v end def self .f1 @@a end end A . new .f #=> 1 A . new .f = 2 A . new .f #=> 2 A .f |
可以看到,@@a在class A定义的类定义内就像全部变量一样的存在,轻松突破gateway scope,可以出现在类定义所在的每个角落。如果是这样的话,那就可以猜测了,class variable表现和其他变量类型不一致,对它而言只有class Xxxx的语句才是真正的gateway scope,其它语句统统ignore掉,包括class << Xxxxx在内。可以用以下代码证明:
1 2 3 4 5 6 | class << A @@a = 1 end @@a #=> 1 self . class #=> Object self . class .class_variables #=> [:@@a] |
看上去class variable定义在了A类中,但其实定义在了main所在类Object中。