64
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]]) => obj

在 mod 的上下文中评估字符串或块。这可用于向类添加方法。

instance_eval的文档:

obj.instance_eval {| | 块 } => 对象

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

4

5 回答 5

110

正如文档所说,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

finallyclass_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实例一样ArrayString,Hash和所有其他类本身都是 的实例Class。您可以通过调用#class它们来检查:

"This Is Confusing".class
=> String

String.class
=> Class

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

于 2009-05-23T00:16:39.010 回答
19

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

Ruby 有多种不同的作用域。根据维基百科,有六个,但似乎缺乏详细的正式文档。毫不奇怪,这个问题涉及的范围类型是instanceclass

当前实例范围由 的值定义self。所有不合格的方法调用都被调度到当前实例,对实例变量的任何引用(看起来像@this)也是如此。

但是,def不是方法调用。by 创建的方法的目标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.

于 2009-08-23T10:08:46.867 回答
4

我想你弄错了。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
于 2009-05-22T23:50:32.683 回答
3

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

这是关于单例方法单例模式的链接(非 ruby​​ 特定的)

于 2009-05-23T00:10:27.197 回答
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​​ 中,如果我们调用一个没有接收器的方法,那么该方法将被调用 on 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.newas in扩展class A end)。

但我想把这个答案比接受的答案更深入一点。instance_eval实际上将您放入其中的方法粘贴在哪里?他们进入singleton接收者的班级!一旦您instance_eval在接收器上调用,ruby 解释器就会打开singleton_class并将块中定义的方法放在 thissingleton_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,因此它成为实例方法。所以你不能在类对象上调用它;您只能在类对象的实例上调用它。

于 2018-05-07T05:36:52.057 回答