Ruby 元类疯狂

Posted

技术标签:

【中文标题】Ruby 元类疯狂【英文标题】:Ruby metaclass madness 【发布时间】:2011-01-27 15:13:11 【问题描述】:

我被困住了。我正在尝试动态定义一个类方法,但我无法理解 ruby​​ 元类模型。考虑以下类:

class Example

  def self.meta; (class << self; self; end); end

  def self.class_instance; self; end

end

Example.class_instance.class # => Class
Example.meta.class           # => Class

Example.class_instance  == Example      # => true
Example.class_instance  == Example.meta # => false

显然这两个方法都返回一个 Class 的实例。但是这两个例子 不一样。他们也有不同的祖先:

Example.meta.ancestors            # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors  # => [Example, Object, Kernel]

区分元类和类实例有什么意义?

我发现,我可以send :define_method 到元类来动态定义一个方法,但是如果我尝试将它发送到类实例,它就不起作用了。至少我可以解决我的问题,但我仍然想了解它为什么会这样工作。

2010 年 3 月 15 日 13:40 更新

以下假设是否正确。

如果我有一个调用 self.instance_eval 并定义一个方法的实例方法,它只会影响该类的特定实例。 如果我有一个调用 self.class.instance_eval(与调用 class_eval 相同)并定义一个方法的实例方法,它将影响该特定类的所有实例,从而产生一个新的实例方法。 如果我有一个调用 instance_eval 并定义一个方法的类方法,它将为所有实例生成一个新的实例方法。 如果我有一个类方法,它在 meta/eigen 类上调用 instance_eval 并定义了一个方法,它将产生一个类方法。

我认为它开始对我有意义。如果类方法中的 self 指向 eigen 类,那肯定会限制您的可能性。如果是这样,就不可能从类方法内部定义实例方法。对吗?

【问题讨论】:

【参考方案1】:

使用instance_eval 动态定义单例方法很简单:

Example.instance_eval def square(n); n*n; end 
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27

至于上面的区别,你混淆了两件事:

您定义的元类,在 Ruby 社区中称为 singelton 类eigen 类。该单例类是您可以添加类(单例)方法的类。

至于您尝试使用class_instance 方法定义的类实例,只不过是类本身,为了证明这一点,只需尝试将实例方法添加到类Example 并检查class_instance您定义的方法通过检查该方法的存在来返回类Example 本身:

class Example
  def self.meta; (class << self; self; end); end
  def self.class_instance; self; end
  def hey; puts hey; end
end

Example.class_instance.instance_methods(false) #=> ['hey']

总之为你总结一下,当你想添加类方法时,只需将它们添加到那个元类。至于class_instance这个方法没用,去掉就好。

无论如何我建议你阅读this post 以掌握一些Ruby反射系统的概念。

更新

我建议你阅读这篇好文章:Fun with Ruby's instance_eval and class_eval, 不幸的是,class_evalinstance_eval 令人困惑,因为它们以某种方式违背了它们的命名!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

现在回答你的假设:

如果我有一个实例方法 调用 self.instance_eval 并定义一个 方法,只会影响 该类的特定实例。

是的:

class Foo
  def assumption1()
    self.instance_eval("def test_assumption_1; puts 'works'; end")
  end
end

f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []

如果我有一个实例方法 调用 self.class.instance_eval (其中 与调用相同 class_eval) 并定义了一个方法 将影响所有实例 特定的类导致一个新的 实例方法。

没有instance_eval 在该上下文中将在类本身上定义单例方法(不是实例方法):

class Foo
  def assumption2()
    self.class.instance_eval("def test_assumption_2; puts 'works'; end")
  end
end

f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]

为此,将instance_eval 替换为上面的class_eval

如果我有一个类方法调用 instance_eval 并定义了一个方法 将产生一个新的实例方法 适用于所有情况。

没有:

class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

Foo.singleton_methods(false) #=> ["assumption_3"]

这将创建单例方法,而不是实例方法。为此,将instance_eval 替换为上面的class_eval

如果我有一个类方法调用 元/特征类上的 instance_eval 并定义将导致的方法 一个类方法。

不,那会做这么复杂的东西,因为它会给单例类添加单例方法,我认为这不会有任何实际用途。

【讨论】:

有关为什么instance_eval 中的def 定义类方法的更多信息,请参阅这篇文章yugui.jp/articles/846 到目前为止非常感谢。我更新我的问题。你介意看一下吗? 非常感谢您的详细回答。我可能需要一些时间才能完全理解 Ruby 类模型的概念。【参考方案2】:

如果您在 上定义方法,则可以在其 对象 上调用它。这是一个实例方法

class Example
end

Example.send :define_method, :foo do
  puts "foo"
end

Example.new.foo
#=> "foo"

如果您在 元类 上定义方法,则可以在 上调用它。这类似于类方法或其他语言中的静态方法的概念。

class Example
  def self.metaclass
    class << self
      self
    end
  end
end

Example.metaclass.send :define_method, :bar do
  puts "bar"
end

Example.bar
#=> "bar"

元类存在的原因是因为您可以在 Ruby 中做到这一点:

str = "hello"
class << str
  def output
    puts self
  end
end

str.output
#=> "hello"

"hi".output
# NoMethodError

如您所见,我们定义了一个仅适用于一个字符串实例的方法。我们定义这个方法的东西叫做元类。在方法查找链中,首先访问元类,然后再搜索对象的类。

如果我们将String 类型的对象替换为Class 类型的对象,您可以想象为什么这意味着我们只在特定 类上定义一个方法,而不是在所有类上类。

当前上下文和self 之间的差异是微妙的,如果您有兴趣,可以read more。

【讨论】:

以上是关于Ruby 元类疯狂的主要内容,如果未能解决你的问题,请参考以下文章

Ruby 与 Python 元类的类比是啥?

给定一个 Ruby 对象的实例,我如何获得它的元类?

Ruby - 在 ruby​​ 类的元类中定义 self 方法

Python 的元类设计起源自哪里?

Python 的元类设计起源自哪里?

为啥具有对象基础的元类会引发元类冲突?