Jak zrozumieć różnicę między klasą eval () a instancją eval ()?

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>

Bazując na nazwie metod, spodziewam się, że class_eval pozwoli Ci dodać metodę klasy do Foo i instance_eval pozwoli Ci dodać metodę instancji do Foo. Ale wydaje się, że robią odwrotnie .

W powyższym przykładzie, jeśli wywołasz class_bar na klasie Foo, otrzymasz niezdefiniowany błąd metody, a jeśli wywołasz instance_bar na instancji zwracanej przez Foo.nowy otrzymasz również niezdefiniowany błąd metody. Oba błędy zdają się przeczyć intuicyjnemu zrozumienie, co class_eval i instance_eval powinny zrobić.

Jaka jest naprawdę różnica między tymi metodami?

Dokumentacja dla class_eval:

Mod.class_eval(string [, nazwa pliku [, lineno]]) = >obj

oblicza łańcuch lub blok w kontekst mod. Można to wykorzystać do dodaj metody do klasy.

Dokumentacja dla instance_eval:

Obj.instance_eval {| / blok } = > obj

oblicza łańcuch zawierający Ruby kod źródłowy, lub dany blok, w kontekście odbiorcy obj). W celu ustalenia kontekstu, zmienna self jest ustawiona na obj podczas kod jest wykonywany, podając kod dostęp do zmiennych instancji obj.

Author: pez_dispenser, 2009-05-23

5 answers

Jak mówi dokumentacja, class_eval ocenia łańcuch znaków lub blok w kontekście modułu lub klasy. Zatem następujące fragmenty kodu są równoważne:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

W każdym przypadku Klasa String została ponownie otwarta i zdefiniowana nowa metoda. Metoda ta jest dostępna we wszystkich instancjach klasy, więc: {]}

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

class_eval ma wiele zalet w porównaniu z prostym ponownym otwarciem klasy. Po pierwsze, możesz łatwo wywołać zmienną, i jest jasne, jaki jest Twój zamiar. Inny zaletą jest to, że zawiedzie, jeśli klasa nie istnieje. Tak więc poniższy przykład nie powiedzie się, ponieważ {[7] } jest niepoprawnie pisany. Jeśli klasa zostanie po prostu ponownie otwarta, to się powiedzie (i zostanie zdefiniowana Nowa nieprawidłowa Klasa Aray):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Wreszcie class_eval może wziąć sznurek, który może być przydatny, jeśli robisz coś bardziej nikczemnego...

instance_eval z drugiej strony ocenia kod względem pojedynczej instancji obiektu:

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

Więc z instance_eval, metoda jest tylko zdefiniowana dla tej pojedynczej instancji ciągu znaków.

Dlaczego więc instance_eval na Class definiuje metody klasowe?

Podobnie jak "This Is Confusing" i "The Smiths on Charlie's Bus" są instancjami String , Array, String, Hash i wszystkie inne klasy same w sobie są instancjami Class. Możesz to sprawdzić, dzwoniąc #class na nich:

"This Is Confusing".class
=> String

String.class
=> Class

Więc kiedy wywołujemy {[10] } robi to samo na klasie, jak na każdym innym obiekcie. Jeśli użyjemy instance_eval do zdefiniowania metody na klasie, to zdefiniuje ona metodę właśnie dla tego instancja klasy, nie wszystkie klasy. Możemy nazwać tę metodę metodą klasy, ale jest to tylko metoda instancji dla danej klasy.

 83
Author: tomafro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-23 07:54:34

Druga odpowiedź jest poprawna, ale pozwól, że zgłęb trochę.

Ruby ma wiele różnych rodzajów zasięgu; sześć według Wikipedii , chociaż brakuje szczegółowej dokumentacji formalnej. Nie dziwi więc fakt, że instancja i klasa .

Bieżący zakres instancji jest zdefiniowany przez wartość self. Wszystkie niekwalifikowane wywołania metod są wysyłane do bieżącej instancji, podobnie jak wszelkie odwołania do zmiennych instancji (które wyglądają jak @this).

Jednakże def nie jest wywołaniem metody. Celem dla metod utworzonych przez def jest bieżąca klasa (lub moduł), którą można znaleźć za pomocą Module.nesting[0].

Zobaczmy, jak te dwa różne smaki eval wpływają na te zakresy:

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

W obu przypadkach zakres instancji jest obiektem, na którym jest wywoływany *_eval.

Dla class_eval, Klasa scope również staje się obiektem docelowym, więc def tworzy instancję metody dla tej klasy / modułu.

Dla instance_eval, klasa scope staje się klasą singleton (aka metaclass, eigenclass) obiektu docelowego. Metody instancji utworzone w klasie singleton dla obiektu stają się metodami Singletona dla tego obiektu. Metody Singletona dla klasy lub modułu są powszechnie (i nieco niedokładnie) nazywane metodami klasy .

Zakres klasy jest również używany do rozwiązywania stałych. Zmienne klasy (@@these @@things) są rozwiązywane za pomocą class scope, ale pomijają klasy singleton podczas przeszukiwania łańcucha zagnieżdżania modułów. Jedynym sposobem, jaki znalazłem, aby uzyskać dostęp do zmiennych klas w klasach singleton jest class_variable_get/set.

 17
Author: jedediah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-23 10:08:46

Chyba się pomyliłeś. class_eval dodaje metodę do klasy, więc wszystkie instancje będą miały metodę. instance_eval doda metodę tylko do jednego konkretnego obiektu.

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
 5
Author: besen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-22 23:50:32

Instance_eval efektywnie tworzy metodę singleton dla danej instancji obiektu. class_eval utworzy normalną metodę w kontekście danej klasy, dostępną dla wszystkich obiektów tej klasy.

Oto link dotyczący metody Singletona i wzór Singletona (nie specyficzne dla ruby)

 3
Author: jess,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-23 00:50:02

instance_eval i class_eval pozwalają na wykonanie fragmentu kodu. Więc co możesz powiedzieć? Staromodni eval potrafią to zrobić. Ale instance_eval i class_eval akceptują argument blokowy dla fragmentu kodu. Więc fragment kodu nie musi być ciągiem znaków. Również instance_eval i class_eval umożliwiają odbiornik (w przeciwieństwie do Starego eval). W związku z tym można wywołać te dwie nowoczesne metody na obiekcie klasy lub nawet na obiekcie instancji.

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

Pamiętaj również w ruby, że jeśli wywołamy metodę bez odbiornika, to metoda zostanie wywołana na self, który w bloku instance_eval jest obiektem, na którym wywołaliśmy instance_eval. zmienne instancji są prywatne w ruby. Nie można uzyskać do nich dostępu poza klasą, w której są zdefiniowane. Ponieważ zmienne instancji są przechowywane w self, możemy uzyskać do nich dostęp w instance_eval (to samo dotyczy prywatnych metod, które nie mogą być wywoływane z odbiornikiem):

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”

Możemy również dodać metody do odbiornika w instance_eval i class_eval. Tutaj dodajemy go do instance_eval:

class A
end

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

A.a_method
# =>  a method

Teraz pomyśl co my tylko przez chwilę. Użyliśmy instance_eval, zdefiniowaliśmy metodę w jej block, a następnie wywołaliśmy metodę na samym obiekcie klasy. Czy to nie metoda klasowa? Potraktuj to jako metodę "klasową", jeśli chcesz. Ale wszystko, co zrobiliśmy, to zdefiniowaliśmy metodę na odbiorniku w bloku instance_eval, A odbiornik okazał się A. Możemy łatwo zrobić to samo na obiekcie instancji:

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

a.a_method
# => a method
I działa tak samo. Nie myśl o metodach klasowych jak o metodach klasowych w innych językach. To tylko metody zdefiniowany na self, Gdy self jest obiektem klasy (rozciągającym się od Class.new jak w class A end).

Ale chcę wziąć tę odpowiedź trochę głębiej niż przyjęta odpowiedź. Gdzie instance_eval właściwie przyklejasz metody, które w nich umieszczasz? Idą do klasy singleton odbiornika! Gdy tylko wywołasz instance_eval na odbiorniku, interpreter ruby otworzy singleton_class i umieści metody zdefiniowane w bloku wewnątrz tego singleton_class. To tak, jakby używać extend w klasie (ponieważ extend otwiera klasę singleton i umieszcza metody w module przekazanym do extend w klasie singleton)! Otwiera singleton_class, która jest częścią hierarchii dziedziczenia (tuż przed klasą nadrzędną): A -> singleton_class -> Parent

Co sprawia, że class_eval jest inny? class_eval mogą być wywoływane tylko na klasach i modułach. self nadal odnosi się do odbiornika:

class A
end

A.class_eval do
  # self is A
  self
end

Ale w przeciwieństwie do instance_eval, kiedy zdefiniujesz metody w bloku class_eval, będą one dostępne na instancjach klasy nie sam obiekt klasy. Z class_eval, metody nie są dodawane do klasy singleton w hierarchii dziedziczenia. Zamiast tego metody są dodawane do current class odbiornika! Kiedy zdefiniujesz metodę w class_eval, ta metoda przechodzi bezpośrednio do current class i w ten sposób staje się metodą instancji. Więc nie można go wywołać na obiekcie klasy; można go wywołać tylko na instancjach obiektu klasy.

 0
Author: Donato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-07 05:42:55