Ruby Ruby 的类变量

zputee · 2013年05月04日 · 最后由 zgm 回复于 2013年05月04日 · 12063 次阅读

昨天(2013-5-4) 本帖子,写的比较匆忙,论证不严谨,更主要水平有限,和@zgm 等等的讨论,深受启发,收益良多,感谢

今天,重新写了一篇。http://ruby-china.org/topics/10715,从类变量读写的角度,更易理解。

附上新贴的三条主要观点。 一、类变量的赋值 在本类的 ancestors,反向查找。(即 Ojbect 中的类变量最优先----从 Object 类开始查找) 1.找到,直接赋值 2.找不到,在本类上创建类变量。 二、类变量的读取 在本类的 ancestors,反向查找。 1.在父类中找到:返回父类中的类变量。(如果本类的有同名的类变量,同时被删除。) 2.在本类中找到:返回本类中的类变量。 3.找不到:报错。 三、singleton 方法定义其他类中时,在外层类及父类中读取类变量(方法与上面两点,基本相同)。


原观点,比较难于理解,所以

加了下划线,没有删除。^_^

在动态编程时,ruby 的类变量很容易出错。 方法中访问类变量 1. 方法定义的外层类的类变量。(外层,方法定义的静态位置!!!,注意 singleton 方法) 2. 外层类的父类的 ancestors,反向查找。Ojbect 中的类变量最优先! 3. inluded_modules 中查找。

类定义中的类变量 1 .本类中的类变量。 2. 父类的 ancestors,反向查找。Ojbect 中的类变量最优先! 3. inluded_modules 中查找。


一、方法的外层类是 Object 类。

class C; @@var = 1; end
def C.hi2
  @@var
end   
C.hi2            #err 因为此时方法的外层类是Ojbect,
self.class.class_variable_defined? :@@var  #=>false, Object类无@@var类变量,所以出错。

#这时可以使用class_eval方法切换 context
C.class_eval %{         
  def self.hi3;@@var; end
}
C.hi3   #=>1

#--多谢12楼 @zgm 指出, 下面D2例子有错---------------------------------
class C2;@@var2 = 200;end 
class D
  def self.hi1          #外层D类中没有定义@@var
    @@var
  end
end
D.hi1   #err
class C2
  def D.hi;@@var;end    # 方法的外层类的类变量@@var=200
end
D.hi   #=>200
#--- 方法的外层类中没有定义@@var=200,所以报错。这验证了关于外层的结论。----------------

二、父类 Object 中的@@var 覆盖 子类 C3 中的@@var

class C3; @@var3=100; end
class C33 < C3           
  def self.hi
     @@var3     #可以访问父类C3中的类变量
  end
end
C33.hi  #=>100

@@var3 = 200    #总父类Object中的@@var3,优先C3中的@@var
C33.hi  #=>>200
class C33
  @@var = 300   #修改的是Object类中的@@var
end
C33.hi     #=>300
@@var   #=>300

三、模块中的类变量(容易出错!!!)

module Mm; @@m=800; end
class CC
  include Mm
  def self.hi
    @@m
  end
end
CC.hi          #=>800
class CC2 < CC
  @@m = 900    #=>覆盖了Mm中的@@m
end
CC2.hi       #=>900
Mm.class_variable_get :@@m  #=>900,已经改变了Mm中的@@m

class CC3
  include Mm
  @@m    #=>这里@@m已经为900了!!!
end

四、class 关键字是类变量的切换门(静态位置)

class Q
  @@q= 1
  class Q2                      #class关键字,类变量门, 访问不到外层了。
    def self.hi
      @@q                      #访问不到外层的@@q
    end
  end
  @@q
end
Q::Q2.hi    #=>err  Q2中无类变量@@q

五、静态位置 > 继承 > include

# 静态位置优先与继承
class W; @@w = 1;end
class W11 < W; end
class W1x 
  @@w =2
  def W11.hi
      @@w
  end
end
W11.hi           #=>2 , ( 静态位置优先与继承)
W11.class_variable_get :@@w  #=>1

#----------------------------------------------
# 继承优先 include
class W2; @@w2=1; end
module M2  @@w2 =2; end
class W22 <W2 
  include M2
  p  @@w2       #=>1, 继承优先 include
end
M2.class_variable_get :@@w2  #=>2

#------------------------------------------------
# 静态位置有先与 include
module M3  @@w3 =2; end
class W3
  include M3
end
class W3x
  @@w3 = 1
  def W3.hi
      @@w3
  end
end
W3.hi     #=>3 , 静态位置有先与 include
W3.class_variable_get :@@w3  #=>2

总结

  • 和常量类似,静态位置> 继承 > inlude
  • class 关键字是类变量空间切换门

  • Object 中的类变量,相当于全局变量。(如果在 Kernel 中猴子补丁可访问?)

  • 父类中的类变量与子类中的同名类变量是 同一个类变量。

  • singleton 方法定义中访问类中的类变量,可以用 class_eval 方法切换

@@好像实现不好,不太推荐用这个。

#1 楼 @chenge :确实不建议使用类变量。动态编程,类变量很容易出错,类变量和常量有许多相通之处,但更复杂些。韩信用兵吧,^_^。 但有两点意义:

  1. 比全局变量好,提倡用 Object 的类变量替代全局变量。
  2. 加深 ruby 语言的理解。

类变量到底哪里不好?

#3 楼 @zgm 易出错。instance_variable,函数参数传递 安全。

类变量是个设计败笔,其它语言有这个设计是因为类不是对象,所以有在所有类之间共享的”类变量“,而 ruby 的类本身就是对象

#5 楼 @zgm 上面都是例子呀 常量和类变量:self (动态)+ scope(静态)都有关。关联多一点,独立性差一点。

#3 楼 @zgm 类变量会沿着继承链去查找,可能在定义一个子类的时候就改变了这个类变量,引起一些不希望的结果

@@v = 1
class A
  @@v = 2
end

puts @@v # => 2

其实这里的@@v本身是Object的类变量,在A定义的时候,就查找到了顶级的@@v,所以直接拿来用,而不是自己又去定义了一个。

然后可以想到,以后所有继承于Object的类中的类变量@@v都会被影响到。

楼主说的一些查找顺序的东西,有没有一些参考资料的?谢啦~

#8 楼 @Tony612 #9 楼 @Tony612 http://cirw.in/blog/constant-lookup.html 这是关于常量的。类变量是我用 pry 做实验得出的结论。多用 pry 或 irb。

class A
  @@v = 2
end
@@v = 1
A.class_variable_get :@@v #=>1

class A
  def hi
    @@v
  end
end 
A.new.hi  #=>1

#10 楼 @zputee 我以为你的意思是 ruby 类变量 实现有 bug,我看一楼这说“@@好像实现不好,不太推荐用这个。”我以为是这个意思,一细看原来是 坑多。

其次你的示例代码有些问题。比如:

class C; @@var = 1; end
def C.hi2
  @@var
end   
C.hi2            #err 因为此时方法的外层类是Ojbect,
self.class.class_variable_defined? :@@var  #=>false, Object类无@@var类变量,所以出错。

#这时可以使用class_eval方法切换 context
C.class_eval %{         
  def self.hi3;@@var; end
}
C.hi3   #=>1

#-------------------------------------
class C2;@@var2 = 200;end 
class D
  def self.hi1          #外层D类中没有定义@@var
    @@var
  end
end
D.hi1   #err

class C2
  def D.hi;@@var;end    # 方法的外层类的类变量@@var=200
end
D.hi   #=>200

前面没问题,后面 D.hi 也不是 200 啊,注释中的外层类也就是 C2 中的 @@var 在哪?本来不太明白的同学一看更糊涂了。

最后这个观点:

  1. 外层类的父类的 ancestors,反向查找。Ojbect 中的类变量最优先!

有点不同看法,我觉得跟 ancestors 的顺序没有什么关系,只有覆盖的关系,主要是看谁的类变量最后被赋值。

而 include 进来 Module 的类变量不会覆盖类体系的类变量,但是假如当前类体系没有类变量,那么会变成类体系的类变量。

以上是我的理解,说的有些乱,如果哪里需要例子尽管提。

#12 楼 @zgm 所以就是 bug

#13 楼 @jjym 如果你觉得是 bug,那你觉得正确应该是什么?

#14 楼 @zgm 你觉得先到 main 里找类变量是对的?我觉得至少这样是不正确的

@jjym 他没有先到 main 里找变量啊

#16 楼 @zgm 好吧。。我刚刚记错了。。不过的确是有 bug..我回想下..

#16 楼 @zgm 我没细看你们讨论,不过我觉得这里的确是个 bug

#17 楼 @jjym @@a 定义在 main 中,main 是 Object 的实例,所以这里定义的类变量可以被 Object 或 Object 的子类 以及他们的实例所使用,Array 属于 Object 的实例,所以 Array 的类变量 @@a 就是 main 中定义的 @@a,这有什么问题?

#20 楼 @jjym 至少你说的那个不是问题。

#21 楼 @zgm 我说的没问题不代表这个没问题啊,这个有没有问题至少和我是无关的

@jjym 我回复这条的时候:

@jjym 他没有先到 main 里找变量啊

你写了一段代码,然后我反驳你写的那段代码,后来你用很快的速度又替换掉了。我不知道什么意思。

#23 楼 @zgm 意思就是 1,你说的是对的 2,我说的是错的 3,类变量的确有问题

#24 楼 @jjym 你说的的确有问题,但是问题在哪呢?

>> class A
>> @@avar = 'hello'
>> end
=> "hello"
 >> A.class_variables
=> ["@@avar"]
 >> A.class_eval { puts @@avar }
=> warning: class variable access from toplevel
NameError: uninitialized class variable @@avar in Object

你说这个?

真不觉得这是什么问题。http://www.ruby-doc.org/core-2.0/Module.html#method-i-class_eval 这里说的很明白了

Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected.

class_eval 后面跟 block 和 string 对寻找常量和类变量是有影响的。

上面的如果换成:

A.class_eval " puts @@avar " 

也就是 lz 总结的那个,就不会有问题了。

API 已经说明白的东西,难道也算问题?

#12 楼 @zgm 多谢。这个例子我是错了。刚才看了会电视大。我有点想当然了,类常量是容易错。不建议用! ^_^。

但不影响结论。 外层是静太外层。@@var 没有在 def D.hi;@@var;end 的外层。

class C2;@@var2 = 200;end 
class D
  def self.hi1          #外层D类中没有定义@@var
    @@var
  end
end
D.hi1   #err

class C2
  def D.hi;@@var;end    # 方法的外层类的类变量@@var=200
end
D.hi   #=>200

#28 楼 @zputee 你这个例子不还是有问题的么?

#27 楼 @zgm 好吧,我弄错了,因为 API 上加上了所以不是问题了 (虽然他们忘记加在 instance_eval 上了)

#12 楼 @zgm 外层类的父类的 ancestors,反向查找。Ojbect 中的类变量最优先!没有错

class A
  @@v = 2
  p  @@v.object_id      #x
  $t = @@v
end
@@v = 1
@@v.object_id           #y

A.class_variable_get :@@v #=>1

class A
   p @@v.object_id      #y  这时 内层的@@v object_id已经变了!
   @@v = 3                 #z
end 

@@v     #=>3              #z
 $t         #-=>2  #  $t没有变 ,外层@@v优先

#29 楼 @zgm 我的意思说我的原例子是有问题。原因和本贴的第一个例子一样。@@v 的定义不是在 singleton 函数的静态外层!

这是个错误可以验证本贴结论的正确:

方法中访问类变量,查找

  1. 方法定义的外层类的类变量。(外层,方法定义的静态位置!!!,注意 singleton 方法)

如果要改的话如下:

class C2;@@var2 = 200;end 
class D
  def self.hi1          #外层D类中没有定义@@var
    @@var2
  end
end
D.hi1   #err

class C3
  @@var2=300
  def D.hi;@@var2;end    # 方法的外层类的类变量@@var2=300
end
D.hi   #=>300

#31 楼 @zputee 你的例子根本不能说明你的结论。

class A
  @@v = 2  # 这个类变量是A 和 A的子类以及他们的实例的类变量。
  p  @@v.object_id    
  $t = @@v
end

@@v = 1 # 同理, 这个 @@v 定义在 main 下面, 是 Object 和 Object 的子类以及他们的实例的类变量, 所以他覆盖了 上面定义的 A 的 @@v。

A.class_variable_get :@@v #=>1 原因就是上面说的。

class C < A
  @@v = 3 # 这个 @@v 就是上面 @@v,这里重新定义成了 3
end

A.class_variable_get :@@v #=> 3

结论,就是你的结论是错的。没什么优先不优先的问题,只要理解了 类变量是谁的,一切就顺理成章了,虽然有些坑难理解,但不至于说 ruby 类变量实现有问题 这么严重吧。

#33 楼 @zgm 😄 看你怎么理解了。 我从来没有说 ruby 类变量有问题! 只是和静态语言不同,有点复杂(对于我)。

位置 scope 优先继承!

class W; @@w = 1;end
class W11 < W; end
class W1x 
  @@w =2
  def W11.hi
      @@w
  end
end
W11.hi           #=>2 , ( 静态位置优先与继承)
W11.class_variable_get :@@w  #=>1

#34 楼 @zputee 哎,怎么说不通呢。

class W; @@w = 1;end
class W11 < W; end
class W1x 
  @@w =2 
  def W11.hi
      @@w
  end
end
W11.hi           #=>2 , ( 静态位置优先与继承)
W11.class_variable_get :@@w  #=>1

W1x 中的 @@w 和 W11 中的 @@w 根本就是两个类变量,也就没有什么优先不优先的问题。

算了,你再体会体会吧。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号