instance_eval的行为

时间:2017-06-28 16:29:51

标签: ruby

我对instance_eval的理解是,如果我有模块M,则以下内容是等效的:

module M
  def foo
    :foo
  end
end

class C
  class << self
    include M
  end
end
puts C.foo

相当于:

module M
  def foo
    :foo
  end
end

class C
end
C.instance_eval do
  include M
end
puts C.foo

但是,第一个示例打印:foo,第二个示例抛出NoMethodError? (Ruby 2.3.0)

在上述两种情况下,如果我更换了:

include M

使用:

def foo
  :foo
end

即直接定义方法而不是包含模块,那么两种情况都会导致定义C.foo方法。我是否应该对包含和直接定义方法之间的这种差异感到惊讶? 或者在instance_eval的上下文中调用include是否有意义?它应该只在class_eval内被调用吗?

2 个答案:

答案 0 :(得分:2)

在每种情况下,您呼叫include的对象是什么?在您的第一个示例中,您在C&C的单身类中调用了include

class C
  class << self
    p self == C.singleton_class
    include M
  end
end
# => true

p C.foo
# => :foo

...因此,include行等同于C.singleton_class.include(M)

然而,在你的第二个例子中,你在C本身上调用了include

class C
end
C.instance_eval do
  p self == C
  include M
end
# => true

p C.foo
# => NoMethodError: undefined method `foo' for C:Class

p C.new.foo
# => :foo

...所以你做的相当于C.include(M),与以下内容相同:

class C
  p self == C
  include M
end
# => true

p C.new.foo
# => :foo

按照你想要的方式工作,就是在C&C的单身人士课上调用instance_eval

class D
end
D.singleton_class.instance_eval do
  p self == D.singleton_class
  include M
end
# => true

p D.foo
# => :foo

答案 1 :(得分:0)

Module#class_eval()Object#instance_eval()非常不同。 instance_eval()仅更改self,而class_eval()同时更改selfcurrent class

与您的示例不同,您可以使用instance_eval更改class_instance变量,因为它们位于对象范围内,因为MyClass是类Class的单例实例。

class MyClass
  @class_instance_var = 100
  @@class_var = 100

  def self.disp
    @class_instance_var
  end

  def self.class_var
    @@class_var
  end


  def some_inst_method
    12
  end
end

MyClass.instance_eval do
  @class_instance_var = 500

  def self.cls_method
    @@class_var = 200
    'Class method added'
  end

  def inst_method
    :inst
  end
end

MyClass.disp
#=> 500

MyClass.cls_method
#=> 'Class method added'

MyClass.class_var
#=> 100

MyClass.new.inst_method
# undefined method `inst_method' for #<MyClass:0x0055d8e4baf320>

用简单的语言。

如果您查看上层类定义代码作为解释器,您会注意到有两个范围class scopeobject scope类vars 实例方法可从object scope访问,但不属于instance_eval()的管辖范围,因此会跳过此类代码。

为什么呢?因为顾名思义,它应该改变Class实例(MyClass)的属性而不是其他对象的属性,如MyClass&#39;任何对象的属性。此外,类变量并不真正属于类 - 它们属于类层次结构。

如果要打开非类的对象,则可以 安全地使用instance_eval()。但是,如果您想打开一个类定义并使用definclude某个模块定义方法,那么class_eval()应该是您的选择。

通过更改current classclass_eval()可以有效地重新打开课程,就像class关键字一样。而且,这是你在这个问题上想要实现的目标。

MyClass.class_eval do
  def inst_method
    :inst
  end
end

MyClass.new.inst_method
#=> :inst