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的实例变量。
答案 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
个实例,Array
,String
,Hash
,所有其他类本身就是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 methods和singleton pattern(非特定红宝石)的链接
答案 4 :(得分:1)
instance_eval
和class_eval
允许您执行一大块代码。那你可能会说什么?老式的eval
可以做到这一点。但instance_eval
和class_eval
接受代码块的块参数。所以代码块不需要是一个字符串。 instance_eval
和class_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_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.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
,因此,它成为实例方法。所以你不能在类对象上调用它;你只能在类对象的实例上调用它。