使用class_eval和instance_eval访问Ruby类变量

我有以下内容:

class Test @@a = 10 def show_a() puts "a: #{@@a}" end class << self @@b = '40' def show_b puts "b: #{@@b}" end end end 

为什么以下工作:

 Test.instance_eval{show_b} b: 40 => nil 

但我不能直接访问@@b

 Test.instance_eval{ @@b } NameError: uninitialized class variable @@b in Object 

同样,以下工作

 t = Test.new t.instance_eval{show_a} a: 10 => nil 

但是以下失败了

 t.instance_eval{ @@a } NameError: uninitialized class variable @@a in Object 

我不明白为什么我不能直接从instance_eval块访问类变量。

我在RubyKaigi派对期间向Matz提出了同样的问题。 我喝醉了一半,但他非常清醒,所以你可以把它作为明确的答案。

Anton是对的 – 你不能通过instance_eval()访问类变量的原因是“只是因为”。 甚至class_eval()也有同样的问题(Matz本人对class_eval()并不完全确定,直到我告诉他我已经尝试过了)。 更具体地说:范围方面,类变量更像是常量而不是实例变量,因此切换self(如instance_eval()和class_eval()do)在访问它们时不会有任何区别。

通常,完全避免类变量可能是个好主意。

编辑:下面的代码用1.8.7和1.9.1测试……看起来情况与1.9.2再次不同:/

实际情况并非如此直截了当。 根据您使用的是1.8还是1.9以及是否使用class_evalinstance_eval ,行为存在差异。

以下示例详细说明了大多数情况下的行为。

我还包括了常量的行为,这是好的衡量标准,因为它们的行为类似于类变量,但不完全相同。

类变量

Ruby 1.8中的class_eval

 class Hello @@foo = :foo end Hello.class_eval { @@foo } #=> uninitialized class variable 

Ruby 1.9中的class_eval

 Hello.class_eval { @@foo } #=> :foo 

因此,当使用class_eval时,在1.9(但不是1.8) 查找类变量

Ruby 1.8 1.9中的instance_eval

 Hello.instance_eval { @@foo } #=> uninitialized class variable Hello.new.instance_eval { @@foo } #=> uninitialized class variable 

使用instance_eval时,似乎没有在1.8 1.9中查找类变量

有趣的是常量的情况:

常量

Ruby 1.8中的class_eval

 class Hello Foo = :foo end Hello.class_eval { Foo } #=> uninitialized constant 

Ruby 1.9中的class_eval

 Hello.class_eval { Foo } #=> :foo 

因此,与类变量一样,常量在1.9中查找,但在class_eval 不在 1.8中class_eval

Ruby 1.8中的instance_eval

 Hello.instance_eval { Foo } #=> uninitialized constant Hello.new.instance_eval { Foo } #=> uninitialized constant 

Ruby 1.9中的instance_eval

 Hello.instance_eval { Foo } #=> uninitialized constant Hello.new.instance_eval { Foo } #=> :foo 

似乎常量查找与Ruby 1.9的类变量查找不太相似。 Hello实例确实可以访问常量,而Hello类则不能。

好吧,可能最好的答案是“只是因为”:简而言之,instance_eval创建了某种单例proc,它通过绑定给定对象来调用。 我同意这听起来有点奇怪,但它就是这样。

如果使用字符串执行instance_eval,您甚至会收到警告,告知您的方法尝试访问类变量:

 irb(main):038:0> Test.new.instance_eval "@@a" (eval):1: warning: class variable access from toplevel singleton method NameError: (eval):1:in `irb_binding': uninitialized class variable ... 

Ruby 2.1

这是我发现访问类变量时最简洁和语义正确的方法:

 class Hello @@foo = :foo_value end Hello.class_variable_get :@@foo #=> :foo_value