Подтвердить что ты не робот

Доступ к переменным класса Ruby с class_eval и instance_eval

У меня есть следующее:

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.

4b9b3361

Ответ 1

Я просто задал тот же вопрос Мацу во время вечеринки RubyKaigi. Я был наполовину пьян, но он был совершенно трезв, поэтому вы можете принять это как окончательный ответ.

Антон прав - причина, по которой вы не можете получить доступ к переменным класса через instance_eval(), "просто потому, что". Даже class_eval() разделяет одну и ту же проблему (сам Матц не был полностью уверен в class_eval(), пока я не сказал ему, что я уже пробовал это). Более конкретно: по объему переменные класса больше похожи на константы, чем переменные экземпляра, поэтому включение self (as instance_eval() и class_eval() do) не будет иметь никакого значения, когда дело доходит до их доступа.

В общем, может быть хорошей идеей вообще избегать переменных класса.

Ответ 2

EDIT: ниже код был протестирован с 1.8.7 и 1.9.1... похоже, ситуация снова отличается от 1.9.2:/

Ситуация на самом деле не так прямолинейна. Существуют различия в поведении в зависимости от того, используете ли вы 1,8 или 1,9 и используете ли вы class_eval или instance_eval.

В приведенных ниже примерах подробно описывается поведение в большинстве ситуаций.

Я также включил поведение констант для хорошей меры, так как их поведение аналогично, но не совсем так же, как переменные класса.

Переменные класса

class_eval в Ruby 1.8:

class Hello
    @@foo = :foo
end

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

class_eval в Ruby 1.9:

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

Итак, переменные класса выглядят в 1.9 (но не в 1.8) при использовании class_eval

instance_eval в Ruby 1.8 и 1.9

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

Кажется, что переменные класса не искали 1,8 или 1,9 при использовании instance_eval

Интересно также, что для констант:

Константы

class_eval в Ruby 1.8

class Hello
    Foo = :foo
end

Hello.class_eval { Foo } #=> uninitialized constant

class_eval в Ruby 1.9

Hello.class_eval { Foo } #=> :foo

Итак, как и с переменными класса, константы просматриваются в 1.9, но не в 1.8 для class_eval

instance_eval в Ruby 1.8

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

instance_eval в Ruby 1.9

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

Похоже, что постоянный поиск не совсем аналогичен переменной класса для Ruby 1.9. A Hello экземпляр делает доступ к константе, а класс Hello не работает.

Ответ 3

Ну, наверное, лучший ответ "просто потому, что": экземпляр instance_eval в двух словах создает какой-то singleton 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 ...

Ответ 4

Ruby 2.1

Это самый сжатый и семантически правильный способ доступа к переменной класса:

class Hello
    @@foo = :foo_value
end

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