如何理解class_eval()和instance_eval()之间的区别?

发布于 2024-07-21 19:42:35 字数 1379 浏览 3 评论 0原文

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(字符串[, 文件名[, 行号]]) => 对象

评估字符串或块 模组的上下文。 这可以用来 向类添加方法。

instance_eval

obj.instance_eval {| | 块} => 对象

计算包含 Ruby 的字符串 源代码或给定的块, 在接收者的上下文中 (对象)。 为了设置上下文, 变量 self 设置为 obj 而 代码正在执行,给出代码 访问 obj 的实例变量。

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>

Just based on the name of the methods, I would expect class_eval to allow you to add a class method to Foo and instance_eval to allow you to add an instance method to Foo. But they seem to do the opposite.

In the example above if you call class_bar on the Foo class you get an undefined method error and if you call instance_bar on the instance returned by Foo.new you also get an undefined method error. Both errors seem to contradict an intuitive understanding of what class_eval and instance_eval should do.

What is really the difference between these methods?

Documentation for class_eval:

mod.class_eval(string [, filename [,
lineno]]) => obj

Evaluates the string or block in the
context of mod. This can be used to
add methods to a class.

Documentation for instance_eval:

obj.instance_eval {| | block } => obj

Evaluates a string containing Ruby
source code, or the given block,
within the context of the receiver
(obj). In order to set the context,
the variable self is set to obj while
the code is executing, giving the code
access to obj’s instance variables.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

柒七 2024-07-28 19:42:35

正如文档所述,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,该方法仅为该单个实例定义的一个字符串。

那么为什么 Class 上的 instance_eval 定义类方法呢?

正如 “This Is Confusing”“The Smiths on Charlie's Bus” 都是 String 实例、ArrayStringHash 和所有其他类本身都是 Class 的实例。 您可以通过对它们调用 #class 来检查这一点:

"This Is Confusing".class
=> String

String.class
=> Class

因此,当我们调用 instance_eval 时,它对类的作用与对任何其他对象的作用相同。 如果我们使用instance_eval在类上定义方法,它将仅为该类的实例而不是所有类定义方法。 我们可以将该方法称为类方法,但它只是该特定类的实例方法。

As the documentation says, class_eval evaluates the string or block in the context of the Module or Class. So the following pieces of code are equivalent:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

In each case, the String class has been reopened and a new method defined. That method is available across all instances of the class, so:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"

class_eval has a number of advantages over simply reopening the class. Firstly, you can easily call it on a variable, and it's clear what your intent is. Another advantage is that it will fail if the class doesn't exist. So the example below will fail as Array is spelt incorrectly. If the class was simply reopened, it would succeed (and a new incorrect Aray class would be defined):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Finally class_eval can take a string, which can be useful if you're doing something a little more nefarious...

instance_eval on the other hand evaluates code against a single object instance:

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

So with instance_eval, the method is only defined for that single instance of a string.

So why does instance_eval on a Class define class methods?

Just as "This Is Confusing" and "The Smiths on Charlie's Bus" are both String instances, Array, String, Hash and all other classes are themselves instances of Class. You can check this by calling #class on them:

"This Is Confusing".class
=> String

String.class
=> Class

So when we call instance_eval it does the same on a class as it would on any other object. If we use instance_eval to define a method on a class, it will define a method for just that instance of class, not all classes. We might call that method a class method, but it is just an instance method for that particular class.

或十年 2024-07-28 19:42:35

另一个答案是正确的,但请允许我深入一点。

Ruby 有多种不同的作用域; 根据维基百科,有六个,但似乎缺乏详细的正式文档。 毫不奇怪,这个问题涉及的范围类型是实例

当前实例范围由self 的值定义。 所有非限定方法调用都将分派到当前实例,对实例变量的任何引用(类似于 @this)也是如此。

但是,def 不是方法调用。 def 创建的方法的目标是当前类(或模块),可以通过 Module.nesting[0] 找到它。

让我们看看两种不同的 eval 风格如何影响这些范围:


String.class_eval { [self, Module.nesting[0]] }
=> [字符串,字符串]
String.instance_eval { [self, Module.nesting[0]] }
=> [字符串,#<类:字符串>]

在这两种情况下,实例范围都是调用 *_eval 的对象。

对于class_eval,类范围也成为目标对象,因此def为该类/模块创建实例方法。

对于instance_eval,类作用域成为目标对象的单例类(又名元类、特征类)。 在对象的单例类上创建的实例方法成为该对象的单例方法。 类或模块的单例方法通常(但有些不准确)称为类方法

类作用域还用于解析常量。 类变量(@@这些@@things)通过类作用域进行解析,但在搜索模块嵌套链时它们会跳过单例类。 我发现访问单例类中的类变量的唯一方法是使用class_variable_get/set

The other answer is correct, but allow me to go in depth a little.

Ruby has a number of different kinds of scope; six according to wikipedia, though detailed formal documentation seems to be lacking. The kinds of scope involved in this question are, not surprisingly, instance and class.

The current instance scope is defined by the value of self. All unqualified method calls are dispatched to the current instance, as are any references to instance variables (which look like @this).

However, def is not a method call. The target for methods created by def is the current class (or module), which can be found with Module.nesting[0].

Let's see how the two different eval flavors affect these scopes:


String.class_eval { [self, Module.nesting[0]] }
=> [String, String]
String.instance_eval { [self, Module.nesting[0]] }
=> [String, #<Class:String>]

In both cases, the instance scope is the object on which *_eval is called.

For class_eval, the class scope also becomes the target object, so def creates instance methods for that class/module.

For instance_eval, the class scope becomes the singleton class (aka metaclass, eigenclass) of the target object. Instance methods created on the singleton class for an object become singleton methods for that object. Singleton methods for a class or module are what are commonly (and somewhat inaccurately) called class methods.

The class scope is also used to resolve constants. Class variables (@@these @@things) are resolved with class scope, but they skip over singleton classes when searching the module nesting chain. The only way I have found to access class variables in singleton classes is with class_variable_get/set.

内心荒芜 2024-07-28 19:42:35

我认为你搞错了。 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

I think you got it wrong. class_eval adds the method in the class, so all instances will have the method. instance_eval will add the method just to one specific object.

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
温柔戏命师 2024-07-28 19:42:35

instance_evalclass_eval 允许您执行一段代码。 那么你会说什么? 老式的eval可以做到这一点。 但 instance_evalclass_eval 接受代码块的块参数。 所以代码块不需要是字符串。 instance_evalclass_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 initialize
    @a = “a”
  end

  private

  def private_a
    return “private a”
  end
end

a = A.new
puts a.instance_eval {  @a }
# => “a”
puts a.instance_eval {  private_a }
# => “private a”

我们还可以在 instance_evalclass_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,在其块中定义了一个方法,然后在类对象本身上调用该方法。 这不是一个类方法吗? 如果您愿意,可以将其视为“类”方法。 但我们所做的只是在 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 中那样)结束)。

但我想比公认的答案更深入地理解这个答案。 instance_eval 实际上将您放入其中的方法粘贴在哪里? 它们进入接收器的 singleton 类! 一旦您在接收器上调用 instance_eval,Ruby 解释器就会打开 singleton_class 并将块中定义的方法放入此 singleton_class 内。 这就像在类中使用extend(因为extend打开了单例类,并将传递给单例类的模块中的方法放置在扩展中)! 它打开 singleton_class,它是继承层次结构的一部分(就在父类之前): 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,方法不会添加到继承层次结构中的单例类中。 相反,这些方法被添加到接收器的当前类! 因此,当您在class_eval中定义一个方法时,该方法会直接进入当前类,因此,它成为一个实例方法。 所以你不能在类对象上调用它; 您只能在类对象的实例上调用它。

instance_eval and class_eval allow you to execute a chunk of code. So what you might say? The old-fashioned eval can do this. But instance_eval and class_eval accept a block argument for the chunk of code. So the chunk of code does not need to be a string. Also instance_eval and class_eval allow a receiver (unlike the old eval). Consequently, you can invoke these two modern methods on a class object or even on an instance object.

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

Also remember in ruby if we call a method without a receiver, then the method would be invoked on self, which in the instance_eval block is the object we invoked instance_eval on. instance variables are private in ruby. You cannot access them outside the class they are defined in. But since instance variables are stored in self, we can access them in instance_eval (the same applies for private methods which cannot be invoked with a receiver):

class A
  def initialize
    @a = “a”
  end

  private

  def private_a
    return “private a”
  end
end

a = A.new
puts a.instance_eval {  @a }
# => “a”
puts a.instance_eval {  private_a }
# => “private a”

We can also add methods to the receiver in instance_eval and class_eval. Here we add it to instance_eval:

class A
end

A.instance_eval do
  def a_method
    puts “a method”
  end
end

A.a_method
# =>  a method

Now think what we just did for a moment. We used instance_eval, defined a method in its block, and then invoked the method on the class object itself. Isn’t that a class method? Think of it as a “class” method if you want. But all we did was define a method on the receiver in the instance_eval block, and the receiver happened to be A. We can easily do the same on a instance object:

 a.instance_eval do
  def a_method
    puts "a method"
  end
end

a.a_method
# => a method

And it works just the same. Don’t think of class methods as class methods in other languages. They are just methods defined on self, when self happens to be a class object (extending from Class.new as in class A end).

But I want to take this answer a little bit more deeper than the accepted answer. Where does instance_eval actually stick the methods you place into them? They go into the singleton class of the receiver! As soon as you invoke instance_eval on the receiver, the ruby interpreter opens the singleton_class and places the methods defined in the block inside this singleton_class. It is just like using extend in a class (because extend opens up the singleton class and places the methods in the module passed to extend into the singleton class)! It opens up the singleton_class, which is part of the inheritance hierarchy (right before the parent class): A -> singleton_class -> Parent

Now what makes class_eval different? class_eval can only be called on classes and modules. self still refers to the receiver:

class A
end

A.class_eval do
  # self is A
  self
end

But unlike instance_eval, when you define methods in the class_eval block, they will be available on instances of the class not the class object itself. With class_eval, the methods are not added to the singleton class in the inheritance hierarchy. Instead the methods are added to the current class of the receiver! So when you define a method in class_eval, that method goes directly into the current class and, thus, it becomes an instance method. So you cannot call it on the class object; you can only call it on instances of the class object.

弄潮 2024-07-28 19:42:35

instance_eval 有效地为相关对象实例创建一个单例方法。 class_eval 将在给定类的上下文中创建一个普通方法,可供该类的所有对象使用。

这是关于 单例方法单例模式(非特定于 ruby​​)

instance_eval effectively creates a singleton method for the object instance in question. class_eval will create a normal method in the given class's context, available to all objects of that class.

Here's a link regarding singleton methods and the singleton pattern(non-ruby specific)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文