如何理解class_eval()和instance_eval()之间的区别?

时间:2009-05-22 23:32:21

标签: ruby class-method instance-method

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添加一个实例方法。但他们似乎做了相反的事情

在上面的例子中,如果你在Foo类上调用class_bar,你会得到一个未定义的方法错误,如果你在Foo.new返回的实例上调用instance_bar,你也会得到一个未定义的方法错误。这两个错误似乎都与对class_eval和instance_eval应该做什么的直观理解相矛盾。

这些方法之间的区别是什么?

class_eval的文档:

  

mod.class_eval(string [,filename [,   lineno]])=&gt; OBJ

     

评估中的字符串或块   mod的上下文这可以用来   将方法添加到类中。

instance_eval的文档:

  

obj.instance_eval {| | block} =&gt; OBJ

     

评估包含Ruby的字符串   源代码或给定的块,   在接收器的上下文中   (OBJ)。为了设置上下文,   变量self设置为obj而   代码正在执行,给出代码   访问obj的实例变量。

5 个答案:

答案 0 :(得分:85)

正如文档所述,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个实例,ArrayStringHash,所有其他类本身就是Class。您可以通过调用#class对其进行检查:

"This Is Confusing".class
=> String

String.class
=> Class

因此,当我们调用instance_eval时,它会在类上执行与在任何其他对象上相同的操作。如果我们使用instance_eval来定义类的方法,它将为该类实例定义一个方法,而不是所有类。我们可以将该方法称为类方法,但它只是该特定类的实例方法。

答案 1 :(得分:17)

另一个答案是正确的,但请允许我深入一点。

Ruby有许多不同的范围;根据{{​​3}}的六个,虽然似乎缺乏详细的正式文件。毫不奇怪,这个问题涉及的范围是实例

当前实例范围由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

答案 2 :(得分:5)

我认为你弄错了。 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 :(得分:3)

instance_eval有效地为相关对象实例创建单例方法。 class_eval将在给定类的上下文中创建一个普通方法,可供该类的所有对象使用。

以下是关于singleton methodssingleton pattern(非特定红宝石)的链接

答案 4 :(得分:1)

instance_evalclass_eval允许您执行一大块代码。那你可能会说什么?老式的eval可以做到这一点。但instance_evalclass_eval接受代码块的块参数。所以代码块不需要是一个字符串。 instance_evalclass_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_evalclass_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一样(因为extend打开单例类并将传递给模块的方法放到单例类中)!它打开了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,因此,它成为实例方法。所以你不能在类对象上调用它;你只能在类对象的实例上调用它。