【问题标题】:How to understand the difference between class_eval() and instance_eval()?如何理解 class_eval() 和 instance_eval() 的区别?
【发布时间】:2010-10-28 09:02:25
【问题描述】:
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(字符串 [, 文件名 [, lineno]]) => 对象

计算字符串或块 mod的上下文。这可以用来 为类添加方法。

instance_eval 的文档:

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

计算一个包含 Ruby 的字符串 源代码或给定的块, 在接收者的上下文中 (对象)。为了设置上下文, 变量 self 设置为 obj 而 代码正在执行,给出代码 访问 obj 的实例变量。

【问题讨论】:

    标签: ruby class-method instance-method


    【解决方案1】:

    如文档所述,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 在一个类上定义一个方法,它将只为该类的实例定义一个方法,而不是所有类。我们可以将该方法称为类方法,但它只是该特定类的实例方法。

    【讨论】:

    • 这是一个了不起的解释。谢谢。我一直跟着你,直到最后一段你试图解释为什么 instance_eval 在类上调用时定义类方法。最后一行对我来说特别难以理解:“将其称为类方法是错误的,它只是特定 Class 实例上的实例方法。”你的意思是所有的类方法都不过如此?
    • 是的,没错——就是这样。明天我会努力把它说得更清楚。
    • 太棒了——我刚刚专门针对这个问题添加了一个新问题:“为什么 instance_eval() 在类上调用时会定义类方法?” stackoverflow.com/questions/900704/….
    【解决方案2】:

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

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

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

    【讨论】:

    • +1 在这里也介绍了范围,除了 self 其他答案。
    • 很好的答案。出于好奇,Module.nesting[0] 是否有任何替代品,因为它在最近的 ruby​​ 版本中似乎无法以这种方式工作?
    【解决方案3】:

    我想你弄错了。 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
    

    【讨论】:

    • 您在对象 foo 上调用 instance_eval,而我在类 Foo 上调用它。我不明白这有什么关系。
    【解决方案4】:

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

    这是关于 singleton methodssingleton pattern(非 ruby​​ 特定)的链接

    【讨论】:

      【解决方案5】:

      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 -&gt; singleton_class -&gt; 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,因此它成为一个实例方法。所以你不能在类对象上调用它;您只能在类对象的实例上调用它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-05-05
        • 2017-04-22
        • 2011-05-23
        • 1970-01-01
        • 2013-05-13
        • 2011-04-01
        • 2011-03-26
        • 2013-08-05
        相关资源
        最近更新 更多