Как понять разницу между class_eval () и instance_eval ()? - PullRequest
52 голосов
/ 23 мая 2009
Foo = Class.new
Foo.class_eval do
  def class_bar
    "class_bar"
  end
end
Foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end
Foo.class_bar       #=> undefined method ‘class_bar’ for Foo:Class
Foo.new.class_bar   #=> "class_bar"
Foo.instance_bar       #=> "instance_bar"
Foo.new.instance_bar   #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>

Только на основе имени методов, Я ожидаю, что class_eval позволит вам добавить метод класса в Foo, а instance_eval позволит вам добавить метод экземпляра в Foo. Но они, кажется, делают наоборот .

В приведенном выше примере, если вы вызываете class_bar для класса Foo, вы получаете неопределенную ошибку метода, а если вы вызываете instance_bar для экземпляра, возвращенного Foo.new, вы также получаете неопределенную ошибку метода. Кажется, что обе ошибки противоречат интуитивному пониманию того, что должны делать class_eval и instance_eval.

В чем разница между этими методами?

Документация для class_eval :

mod.class_eval (строка [, имя файла [, бельё]]) => obj

Оценивает строку или блок в контекст мод. Это может быть использовано для добавить методы в класс.

Документация для instance_eval :

obj.instance_eval {| | блок} => obj

Оценивает строку, содержащую Ruby исходный код или данный блок, в контексте получателя (OBJ). Для того, чтобы установить контекст, переменная self установлена ​​в obj, а код выполняется, давая код доступ к переменным экземпляра obj.

Ответы [ 5 ]

87 голосов
/ 23 мая 2009

Как сказано в документации, class_eval оценивает строку или блок в контексте модуля или класса. Таким образом, следующие части кода эквивалентны:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

В каждом случае класс String открывался заново и определялся новый метод. Этот метод доступен во всех экземплярах класса, поэтому:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"

class_eval имеет ряд преимуществ перед простым открытием класса. Во-первых, вы можете легко вызвать ее для переменной, и вам станет ясно, каково ваше намерение. Другое преимущество состоит в том, что он потерпит неудачу, если класс не существует. Таким образом, приведенный ниже пример потерпит неудачу, поскольку Array написано неправильно Если класс будет просто открыт заново, он будет успешным (и будет определен новый неправильный класс Aray):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Наконец class_eval может взять строку, которая может быть полезна, если вы делаете что-то немного более гнусное ...

instance_eval, с другой стороны, оценивает код для одного экземпляра объекта:

confusing = "This Is Confusing"
confusing.instance_eval do
  def lowercase
    self.downcase
  end
end   

confusing.lowercase
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie's Bus":String

То есть, с instance_eval метод определен только для этого единственного экземпляра строки.

Так почему instance_eval на Class определяет методы класса?

Так же, как "This Is Confusing" и "The Smiths on Charlie's Bus" являются экземплярами String, Array, String, Hash и все остальные классы сами являются экземплярами Class. Вы можете проверить это, позвонив по номеру #class:

"This Is Confusing".class
=> String

String.class
=> Class

Поэтому, когда мы вызываем instance_eval, он делает то же самое для класса, как и для любого другого объекта. Если мы используем instance_eval для определения метода в классе, он будет определять метод только для этого экземпляра класса, а не для всех классов. Мы можем назвать этот метод методом класса, но это просто метод экземпляра для этого конкретного класса.

17 голосов
/ 23 августа 2009

Другой ответ правильный, но позвольте мне немного углубиться.

Ruby имеет множество различных видов областей применения; шесть согласно википедии , хотя подробных формальных документов, похоже, не хватает. Неудивительно, что виды охвата этого вопроса включают экземпляр и класс .

Текущая область действия экземпляра определяется значением self. Все неквалифицированные вызовы методов отправляются текущему экземпляру, как и любые ссылки на переменные экземпляра (которые выглядят как @this).

Однако def не является вызовом метода. Целью для методов, созданных def, является текущий класс (или модуль), который можно найти с помощью Module.nesting[0].

Давайте посмотрим, как два разных eval-аромата влияют на эти области:

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

В обоих случаях область действия экземпляра - это объект, для которого вызывается * _eval.

Для class_eval область действия класса также становится целевым объектом, поэтому def создает методы экземпляра для этого класса / модуля.

Для instance_eval область действия класса становится одноэлементным классом (он же метакласс, собственный класс) целевого объекта. Методы экземпляра, созданные в одноэлементном классе для объекта, становятся одноэлементными методами для этого объекта. Синглтон-методы для класса или модуля - это то, что обычно (и несколько неточно) называется методами класса .

Область видимости класса также используется для разрешения констант. Переменные класса (@@these @@things) разрешаются с помощью области действия класса, но они пропускают одноэлементные классы при поиске в цепочке вложений модуля. Единственный способ, которым я нашел доступ к переменным класса в одноэлементных классах, - это class_variable_get/set.

5 голосов
/ 23 мая 2009

Я думаю, вы ошиблись. class_eval добавляет метод в класс, поэтому у всех экземпляров будет метод. instance_eval добавит метод только к одному конкретному объекту.

foo = Foo.new
foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end

foo.instance_bar      #=> "instance_bar"
baz = Foo.new
baz.instance_bar      #=> undefined method
3 голосов
/ 23 мая 2009

instance_eval эффективно создает одноэлементный метод для рассматриваемого экземпляра объекта. class_eval создаст обычный метод в контексте данного класса, доступный для всех объектов этого класса.

Вот ссылка, касающаяся одноэлементных методов и одноэлементного шаблона (не для ruby)

1 голос
/ 07 мая 2018

instance_eval и class_eval позволяют выполнить фрагмент кода. Так что вы можете сказать? Старомодный eval может сделать это. Но instance_eval и class_eval принимают аргумент блока для фрагмента кода. Таким образом, фрагмент кода не обязательно должен быть строкой. Также instance_eval и class_eval позволяют приемник (в отличие от старого eval). Следовательно, вы можете вызывать эти два современных метода для объекта класса или даже для объекта экземпляра.

class A
end

A.instance_eval do
  # self refers to the A class object
  self
end

a = A.new

a.instance_eval do
  # self refers to the a instance object
  self
end

Также помните, что в ruby, если мы вызываем метод без получателя, метод будет вызываться на self, который в блоке instance_eval является объектом, на который мы вызвали instance_eval. Переменные экземпляра являются частными в ruby. Вы не можете получить к ним доступ вне класса, в котором они определены. Но поскольку переменные экземпляра хранятся в self, мы можем получить к ним доступ в instance_eval (то же самое относится и к закрытым методам, которые нельзя вызвать с помощью получателя):

class A
  def initialzie
    @a = “a”
  end

  private

  def private_a
    puts “private a”
  end
end

a = A.new
puts a.instance_eval {  @a }
# => “a”
puts a.instance_eval {  private_a }
# => “private a”

Мы также можем добавить методы к получателю в instance_eval и class_eval. Здесь мы добавляем его к instance_eval:

class A
end

A.instance_eval do
  def a_method
    puts “a method”
  end
end

A.a_method
# =>  a method

Теперь подумайте, что мы только что сделали. Мы использовали instance_eval, определили метод в его block, а затем вызвали метод для самого объекта класса. Разве это не метод класса? Думайте об этом как о методе «класса», если хотите. Но все, что мы сделали, это определили метод для получателя в блоке instance_eval, и получатель оказался A. Мы можем легко сделать то же самое для объекта экземпляра:

 a.instance_eval do
  def a_method
    puts "a method"
  end
end

a.a_method
# => a method

И работает точно так же. Не думайте о методах класса как о методах класса в других языках. Это просто методы, определенные в self, когда self оказывается объектом класса (начиная с Class.new, как в class A end).

Но я хочу взять этот ответ немного глубже, чем принятый ответ. Где instance_eval на самом деле придерживается методов, которые вы помещаете в них? Они входят в класс singleton приемника! Как только вы вызываете instance_eval на приемнике, интерпретатор ruby ​​открывает singleton_class и помещает методы, определенные в блоке, в этот singleton_class. Это похоже на использование extend в классе (так как extends открывает одноэлементный класс и помещает методы в модуль, переданный для расширения в одноэлементный класс)! Он открывает singleton_class, который является частью иерархии наследования (прямо перед родительским классом): A -> singleton_class -> Parent

Теперь, что отличает class_eval? class_eval можно вызывать только для классов и модулей. self все еще относится к получателю:

class A
end

A.class_eval do
  # self is A
  self
end

Но в отличие от instance_eval, когда вы определяете методы в блоке class_eval, они будут доступны в экземплярах класса, а не в самом объекте класса. При class_eval методы не добавляются в одноэлементный класс в иерархии наследования. Вместо этого методы добавляются в current class получателя! Поэтому, когда вы определяете метод в class_eval, этот метод переходит непосредственно в current class и, таким образом, он становится методом экземпляра. Таким образом, вы не можете вызвать его для объекта класса; Вы можете вызывать его только на экземплярах объекта класса.

...