ruby class_eval、class_exec、module_eval和module_exec之间的区别是什么?

h79rfbju  于 2023-01-16  发布在  Ruby
关注(0)|答案(3)|浏览(185)

我正在阅读Module文档,但似乎无法理解它们的区别,以及应该在哪里使用它们。
evalexec有何不同?

1szpjjfi

1szpjjfi1#

我将在您的问题中包含instance_{eval|exec},以回答更多的问题。
{instance|module|class}_{eval|exec}的所有变化都会改变 * 当前上下文 *,即self的值:

class Array
  p self                     # prints "Array"
  43.instance_eval{ p self } # prints "43"
end

eval版本接受字符串或块,而exec版本只接受块,但允许向其传递参数:

def example(&block)
  42.instance_exec("Hello", &block)
end
example{|mess| p mess, self } # Prints "Hello" then "42"

eval版本不允许传递参数,它提供了self作为第一个参数,尽管我想不出它的用途。
最后,module_{eval|exec}与对应的class_{eval|exec}相同,但是它们与instance_{eval|exec}略有不同,因为它们以不同的方式改变了当前打开的类(即,将受def影响的类):

String.instance_eval{ def foo; end }
Integer.class_eval  { def bar; end }

String.method_defined?(:foo)            # => false
String.singleton_methods.include?(:foo) # => true
Integer.method_defined?(:bar)           # => true

因此obj.instance_{eval|exec}打开obj的单例类,而mod.{class|module}_{eval|exec}打开mod本身。
当然,instance_{eval|exec}在任何Ruby对象(包括模块)上都可用,而{class|module}_*只在Module上可用(因此Classes也可用)

0md85ypi

0md85ypi2#

首先回答您的最后一个问题,eval(在其所有变体中)与exec * 完全 * 不同。exec $command将启动一个新进程来运行您指定的命令,然后在该进程完成时退出。
class_evalmodule_eval具有重定义类和模块的能力--甚至那些不是您自己编写的类和模块。例如,您可以使用class eval添加一个不存在的新方法。

Fixnum.class_eval { def number; self; end }
7.number # returns '7'

class_eval可用于添加示例方法,instance_eval可用于添加类方法(是的,这部分非常混乱)类方法可能类似于Thing.foo--您实际上是在Thing类上调用foo方法。示例方法类似于上面的示例,使用class_eval,我为Fixnum的每个示例添加了一个number方法。
好了,这就是*_eval类的方法,exec方法也是类似的,但是它们允许你查看类的内部,并执行一段代码,就好像它被定义为类的一个方法一样,也许你有一个类看起来像这样:

class Foo
  @@secret = 'secret key'
  @@protected = 'some secret value'
  def protected(key)
    if key == @@secret
       return @@protected
    end
  end
end

Foo只是一个封装了某个秘密值的封装器,如果你知道正确的密钥的话,但是,你可以通过在类的上下文中执行一个块来欺骗类,让它告诉你它的秘密,如下所示:

Foo.class_exec { @@secret = 'i'm a hacker' }
Foo.protected('i'm a hacker') #returns the value of @@protected because we overwrote @@secret

总的来说,有很多Ruby工具,你可以用这些来解决很多问题,很多时候你甚至不需要这样做,除非你想给你使用的某个库定义的类打补丁(尽管这打开了一整罐蠕虫)。试着在IRB中玩它们,看看你觉得哪个更容易。我个人不“我不像使用*_eval方法那样频繁地使用*_exec方法,但这是我个人的偏好。

ekqde3dh

ekqde3dh3#

为了避免歧义,我将把一个属于(拥有)单例类的方法称为单例方法,其余的都是示例方法,尽管有人可能会说一个对象的单例方法是它的单例类的示例方法。

tl;dr在类/模块上使用class_eval/module_eval来定义示例方法,在类/模块上使用instance_eval来定义类方法(或者更精确地说,使用instance_eval来定义单例方法)。另外,您可以使用instance_eval来访问示例变量。

ruby维护了一个类引用的堆栈(简称cref)。当你打开/重新打开一个类时,相应的类引用被推到堆栈中。并且当前的类引用影响def定义方法的位置(它们被添加到哪个类/模块)。
现在,class_eval/module_evalclass_exec/module_exec是别名。
*_exec()变体不接受字符串,但允许向块传递参数。由于*_eval()变体是主要使用的,所以我将重点介绍它们。
class_eval/module_evalcrefself更改为接收器(Thing.module_eval(...)中的Thing):

  • x1个月20个月1x-〉x1个月21个月1x
  • yield_under()(用于块)
  • vm_cref_push()
  • eval_under()(用于字符串)
  • vm_cref_push()

instance_evalcref更改为接收方的单例类,并将self更改为接收方。
让我们来看看他们的行动:

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

因此,在instance_evaldef中,向接收器添加了一个单例方法(接收器的单例类的示例方法),对于类/模块,这意味着类/模块方法,对于其他对象,这意味着可用于该特定对象的方法。

A.class_eval do
  p self                #=> A
  p @a                  #=> 1
  def m() puts 'm' end
end
p A.instance_methods(false).include? :m  #=> true
A.new.m                                  #=> m

并且,在class_eval中,def向接收器本身(类/模块)添加了一个示例方法。class_eval仅适用于类/模块。
此外,当class_eval传递一个块时,常量/类变量查找不受影响:

module A
  C = 1
  @@c = 1
  class B
    C = 2
    @@c = 2
  end
  A::B.class_eval { p [C, @@c] }  #=> [1, 1]
  A::B.class_eval 'p [C, @@c]'    #=> [2, 2]
end

命名是混乱的。我可能会猜测instance_eval中的instance暗示receiver被视为一个示例(允许为特定示例更改内容),而class_eval中的class暗示receiver被视为一个类(允许为一个对象类更改内容)。

相关问题