我对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
内被调用吗?
答案 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()
同时更改self
和current 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 scope
和object scope
。 类vars 和实例方法可从object scope
访问,但不属于instance_eval()
的管辖范围,因此会跳过此类代码。
为什么呢?因为顾名思义,它应该改变Class
实例(MyClass)的属性而不是其他对象的属性,如MyClass
&#39;任何对象的属性。此外,类变量并不真正属于类 - 它们属于类层次结构。
如果要打开非类的对象,则可以
安全地使用instance_eval()
。但是,如果您想打开一个类定义并使用def
或include
某个模块定义方法,那么class_eval()
应该是您的选择。
通过更改current class
,class_eval()
可以有效地重新打开课程,就像class
关键字一样。而且,这是你在这个问题上想要实现的目标。
MyClass.class_eval do
def inst_method
:inst
end
end
MyClass.new.inst_method
#=> :inst