Accessing Ruby Class Variables with class_eval and instance_eval
I have the following:
class Test
@@a = 10
def show_a()
puts "a: #{@@a}"
end
class << self
@@b = '40'
def show_b
puts "b: #{@@b}"
end
end
end
Why does following work:
Test.instance_eval{show_b}
b: 40
=> nil
But I can't access @@b
directly?
Test.instance_eval{ @@b }
NameError: uninitialized class variable @@b in Object
Likewise, the following works
t = Test.new
t.instance_eval{show_a}
a: 10
=> nil
but the following fails
t.instance_eval{ @@a }
NameError: uninitialized class variable @@a in Object
I don't understand why I can't access the Class Variables directly from the in开发者_Python百科stance_eval
blocks.
I just asked the same question to Matz during the RubyKaigi party. I was half-drunk, but he was perfectly sober, so you can take this as the definitive answer.
Anton is right - the reason why you cannot access class variables through instance_eval() is "just because". Even class_eval() shares the same issue (Matz himself wasn't totally sure about class_eval() until I told him I'd already tried it). More specifically: scope-wise, class variables are more like constants than instance variables, so switching self (as instance_eval() and class_eval() do) is not going to make any difference when it comes to accessing them.
In general, it might be a good idea to avoid class variables altogether.
EDIT: below code was tested with 1.8.7 and 1.9.1...it seems the situation is different again with 1.9.2 :/
The situation actually isn't that straight forward. There are differences in behaviour depending on whether you're using 1.8 or 1.9 and whether you're using class_eval
or instance_eval
.
The examples below detail the behaviour in most situations.
I also included the behaviour of constants, for good measure, as their behaviour is similar to, but not exactly the same as, class variables.
Class variables
class_eval
in Ruby 1.8:
class Hello
@@foo = :foo
end
Hello.class_eval { @@foo } #=> uninitialized class variable
class_eval
in Ruby 1.9:
Hello.class_eval { @@foo } #=> :foo
So class variables are looked up in 1.9 (but not in 1.8) when using class_eval
instance_eval
in Ruby 1.8 and 1.9
Hello.instance_eval { @@foo } #=> uninitialized class variable
Hello.new.instance_eval { @@foo } #=> uninitialized class variable
It appears class variables are not looked up in 1.8 or 1.9 when using instance_eval
What is also interesting is the case for constants:
Constants
class_eval
in Ruby 1.8
class Hello
Foo = :foo
end
Hello.class_eval { Foo } #=> uninitialized constant
class_eval
in Ruby 1.9
Hello.class_eval { Foo } #=> :foo
So, as with class variables, constants are looked up in 1.9 but not in 1.8 for class_eval
instance_eval
in Ruby 1.8
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> uninitialized constant
instance_eval
in Ruby 1.9
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> :foo
It appears that constant lookup is not quite analogous to class variable look up for Ruby 1.9. A Hello
instance does get access to the constant while the Hello
class does not.
Well, probably the best answer is "just because": the instance_eval in a nutshell creates some kind of singleton proc that is invoked with the binding of a given object. I agree that is sounds a bit strange, but it is what it is.
If you execute instance_eval with a string, you will even get a warning that your method tries to access class variable:
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
This is the most concise and semantically correct way I've found to access a class variable:
class Hello
@@foo = :foo_value
end
Hello.class_variable_get :@@foo #=> :foo_value
精彩评论