55 votes

Comment comprendre la différence entre class_eval() et instance_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>

Simplement en se basant sur le nom des méthodes, je m'attends à ce class_eval pour vous permettre d'ajouter une méthode de classe, Toto et instance_eval pour vous permettre d'ajouter une méthode d'instance de Foo. Mais ils semblent faire le contraire.

Dans l'exemple ci-dessus si vous appelez class_bar sur la classe Foo vous obtenez une méthode non définie erreur et si vous appelez instance_bar sur l'instance retournée par Foo.nouveau, vous obtenez également une méthode non définie erreur. Les erreurs semblent contredire une compréhension intuitive de ce que class_eval et instance_eval devrait le faire.

Ce qui est vraiment la différence entre ces méthodes?

Documentation pour class_eval:

mod.class_eval(string [, filename [, lineno]]) => obj

Évalue la chaîne ou d'un bloc dans le contexte du mod. Ceci peut être utilisé pour ajouter des méthodes à une classe.

Documentation pour instance_eval:

obj.instance_eval {| | bloc } => obj

Évalue une chaîne de caractères contenant Ruby le code source, ou le bloc donné, dans le contexte du récepteur (obj). Afin de définir le contexte, la variable auto est définie à l'obj tandis que le code est en cours d'exécution, en donnant le code l'accès à l'obj variables d'instance.

92voto

tomafro Points 3852

Que dit la documentation, class_eval évalue la chaîne ou d'un bloc dans le cadre du Module ou de la Classe. Donc la suite des morceaux de code sont équivalentes:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

Dans chaque cas, la classe String a été réouvert et une nouvelle définition de la méthode. Cette méthode est disponible dans toutes les instances de la classe, donc:

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

class_eval a un certain nombre d'avantages par rapport à la simple réouverture de la classe. Tout d'abord, vous pouvez facilement appeler une variable, et c'est clairement ce que votre but est. Un autre avantage est qu'il va échouer si la classe n'existe pas. Ainsi l'exemple ci-dessous échoue Array est exprimée de manière incorrecte. Si la classe était tout simplement rouvert, il serait de réussir (et un nouveau incorrect Aray classe serait défini):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Enfin class_eval pouvez prendre une corde, ce qui peut être utile si vous êtes en train de faire quelque chose d'un peu plus infâme...

instance_eval sur l'autre main évalue code contre une seule instance de l'objet:

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

Donc, avec instance_eval, la méthode n'est définie que pour une instance unique d'une chaîne.

Alors pourquoi est - instance_eval sur Class définir les méthodes de la classe?

Tout comme "This Is Confusing" et "The Smiths on Charlie's Bus" sont à la fois String des cas, Array, String, Hash et toutes les autres classes sont elles-mêmes des instances de Class. Vous pouvez le vérifier en appelant #class sur les:

"This Is Confusing".class
=> String

String.class
=> Class

Ainsi, lorsque nous appelons instance_eval il fait de même sur une classe comme il le ferait sur n'importe quel autre objet. Si nous utilisons instance_eval afin de définir une méthode d'une classe, il permettra de définir une méthode pour seulement une instance de la classe, et pas toutes les classes. Nous pourrions appeler cette méthode à une méthode de classe, mais c'est juste une méthode d'instance de la classe particulière.

18voto

jedediah Points 590

L'autre réponse est correcte, mais permettez-moi d'aller en profondeur un peu.

Ruby a un certain nombre de différents types de portée; six selon wikipedia, bien formalisé et détaillé de la documentation semble être en manque. Les types de champ impliqués dans cette question, il n'est pas surprenant, d'instance et de classe.

L'instance actuelle de la portée est définie par la valeur de self. Tous les appels de méthode non qualifiés sont expédiés à l'instance en cours, comme le sont toutes les références à des variables d'instance (qui ressemblent @this).

Toutefois, def n'est pas un appel de méthode. La cible pour les méthodes créées par def , est actuellement la classe (ou le module), qui peuvent être trouvés avec des Module.nesting[0].

Nous allons voir comment les deux eval saveurs affecter ces étendues:

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

Dans les deux cas, l'instance portée est l'objet sur lequel *_eval est appelé.

Pour class_eval, de l'étendue de classe devient également l'objet cible, alors def crée les méthodes d'instance de la classe/du module.

Pour instance_eval, la classe devient la classe singleton (aka métaclasse, eigenclass) de l'objet cible. Les méthodes d'Instance créée sur la classe singleton pour un objet devenu singleton méthodes de cet objet. Singleton méthodes d'une classe ou d'un module sont généralement (et un peu à tort) appelle les méthodes de la classe.

L'étendue de classe est également utilisé pour résoudre des constantes. Variables de classe (@@these @@things) sont résolus à portée de classe, mais ils ignorer singleton classes lors de la recherche du module de nidification de la chaîne. Le seul moyen que j'ai trouvé pour accéder aux variables de classe dans les classes singleton est avec class_variable_get/set.

5voto

besen Points 10430

Je pense que vous vous êtes trompé. class_eval ajoute la méthode de la classe, de sorte que toutes les instances de la méthode. instance_eval va ajouter de la méthode à un objet spécifique.

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

3voto

jess Points 658

instance_eval crée effectivement un singleton méthode pour l'instance de l'objet en question. class_eval va créer une méthode dans la classe donnée de contexte, à la disposition de tous les objets de cette classe.

Voici un lien concernant singleton méthodes et le pattern singleton(non-ruby spécifique)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X