class A
p self #=> A
@a = 1
def initialize
@b = 2
end
end
p A.instance_variables #=> [:@a]
p A.new.instance_variables #=> [:@b]
类级别的@a将示例变量作为对象添加到类A中,我在这里添加它是为了完整性,但这不是添加类变量的方法。
A.instance_eval do
p self #=> A
p @a #=> 1
def m() puts 'm' end
end
sclass = A.singleton_class
p sclass.instance_methods(false).include? :m #=> true
A.m #=> m
a = A.new
a.instance_eval do
p self #=> #<A:0x00007fc497661be8 @b=2>
p @b #=> 2
def m2() puts 'm2' end
end
sclass = a.singleton_class
p sclass.instance_methods(false).include? :m2 #=> true
a.m2 #=> m2
3条答案
按热度按时间1szpjjfi1#
我将在您的问题中包含
instance_{eval|exec}
,以回答更多的问题。{instance|module|class}_{eval|exec}
的所有变化都会改变 * 当前上下文 *,即self
的值:eval
版本接受字符串或块,而exec
版本只接受块,但允许向其传递参数:eval
版本不允许传递参数,它提供了self
作为第一个参数,尽管我想不出它的用途。最后,
module_{eval|exec}
与对应的class_{eval|exec}
相同,但是它们与instance_{eval|exec}
略有不同,因为它们以不同的方式改变了当前打开的类(即,将受def
影响的类):因此
obj.instance_{eval|exec}
打开obj
的单例类,而mod.{class|module}_{eval|exec}
打开mod
本身。当然,
instance_{eval|exec}
在任何Ruby对象(包括模块)上都可用,而{class|module}_*
只在Module
上可用(因此Classes
也可用)0md85ypi2#
首先回答您的最后一个问题,eval(在其所有变体中)与exec * 完全 * 不同。
exec $command
将启动一个新进程来运行您指定的命令,然后在该进程完成时退出。class_eval
和module_eval
具有重定义类和模块的能力--甚至那些不是您自己编写的类和模块。例如,您可以使用class eval添加一个不存在的新方法。class_eval
可用于添加示例方法,instance_eval
可用于添加类方法(是的,这部分非常混乱)类方法可能类似于Thing.foo
--您实际上是在Thing
类上调用foo
方法。示例方法类似于上面的示例,使用class_eval
,我为Fixnum
的每个示例添加了一个number
方法。好了,这就是
*_eval
类的方法,exec方法也是类似的,但是它们允许你查看类的内部,并执行一段代码,就好像它被定义为类的一个方法一样,也许你有一个类看起来像这样:类
Foo
只是一个封装了某个秘密值的封装器,如果你知道正确的密钥的话,但是,你可以通过在类的上下文中执行一个块来欺骗类,让它告诉你它的秘密,如下所示:总的来说,有很多Ruby工具,你可以用这些来解决很多问题,很多时候你甚至不需要这样做,除非你想给你使用的某个库定义的类打补丁(尽管这打开了一整罐蠕虫)。试着在IRB中玩它们,看看你觉得哪个更容易。我个人不“我不像使用
*_eval
方法那样频繁地使用*_exec
方法,但这是我个人的偏好。ekqde3dh3#
为了避免歧义,我将把一个属于(拥有)单例类的方法称为单例方法,其余的都是示例方法,尽管有人可能会说一个对象的单例方法是它的单例类的示例方法。
tl;dr在类/模块上使用
class_eval
/module_eval
来定义示例方法,在类/模块上使用instance_eval
来定义类方法(或者更精确地说,使用instance_eval
来定义单例方法)。另外,您可以使用instance_eval
来访问示例变量。ruby
维护了一个类引用的堆栈(简称cref
)。当你打开/重新打开一个类时,相应的类引用被推到堆栈中。并且当前的类引用影响def
定义方法的位置(它们被添加到哪个类/模块)。现在,
class_eval
/module_eval
和class_exec
/module_exec
是别名。*_exec()
变体不接受字符串,但允许向块传递参数。由于*_eval()
变体是主要使用的,所以我将重点介绍它们。class_eval
/module_eval
将cref
和self
更改为接收器(Thing.module_eval(...)
中的Thing
):yield_under()
(用于块)vm_cref_push()
eval_under()
(用于字符串)vm_cref_push()
instance_eval
将cref
更改为接收方的单例类,并将self
更改为接收方。让我们来看看他们的行动:
类级别的
@a
将示例变量作为对象添加到类A
中,我在这里添加它是为了完整性,但这不是添加类变量的方法。因此,在
instance_eval
def
中,向接收器添加了一个单例方法(接收器的单例类的示例方法),对于类/模块,这意味着类/模块方法,对于其他对象,这意味着可用于该特定对象的方法。并且,在
class_eval
中,def
向接收器本身(类/模块)添加了一个示例方法。class_eval
仅适用于类/模块。此外,当
class_eval
传递一个块时,常量/类变量查找不受影响:命名是混乱的。我可能会猜测
instance_eval
中的instance
暗示receiver被视为一个示例(允许为特定示例更改内容),而class_eval
中的class
暗示receiver被视为一个类(允许为一个对象类更改内容)。