Accessing Ruby Class Variables with class_eval and instance_eval

北慕城南 提交于 2019-12-02 16:43:35

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!