10

Python 有元类的想法,如果我理解正确的话,它允许您在构造时修改类的对象。您不是在修改类,而是要创建然后初始化的对象。

Python(我相信至少从 3.0 开始)也有类装饰器的想法。同样,如果我理解正确,类装饰器允许在声明类定义时对其进行修改。

现在我相信 Ruby 中的类装饰器有一个或多个等效功能,但我目前不知道与元类等效的东西。我敢肯定,您可以通过一些函数轻松地抽取任何 Ruby 对象并对其执行任何操作,但是该语言中是否有一个特性可以像元类一样设置它?

再说一遍,Ruby 是否有类似于 Python 的元类的东西?

编辑我对 Python 的元类很感兴趣。元类和类装饰器做的事情看起来非常相似。它们都在定义类时修改类,但方式不同。希望 Python 大师能进来并更好地解释 Python 中的这些特性。

但是一个类或一个类的父类可以实现一个__new__(cls[,..])函数,该函数在用 初始化对象之前定制它的构造__init__(self[,..])

编辑这个问题主要是为了讨论和学习两种语言在这些特性上的比较。我熟悉 Python 但不熟悉 Ruby 并且很好奇。希望其他对这两种语言有相同问题的人会发现这篇文章很有帮助和启发。

4

2 回答 2

24

Ruby 没有元类。Ruby 中有一些结构,有些人有时会错误地将其称为元类,但实际上并非如此(这是无穷无尽的混乱的根源)。

但是,有很多方法可以在 Ruby 中实现与元类相同的结果。但是,如果不告诉我们您到底想做什么,就无法知道这些机制可能是什么。

简而言之:

  • Ruby 没有元类
  • Ruby 没有任何一种结构与 Python 的元类相对应
  • Python 可以用元类做的所有事情也可以在 Ruby 中完成
  • 但是没有单一的构造,您将使用不同的构造,具体取决于您想要做什么
  • 这些构造中的任何一个都可能具有其他不对应于元类的特性(尽管它们可能对应于 Python中的其他东西)
  • 虽然你可以在 Ruby 中做任何你可以在 Python 中使用元类做的事情,但它可能并不一定很简单
  • 虽然通常会有更优雅的 Rubyish解决方案
  • 最后但同样重要的是:虽然你可以在 Ruby 中做任何你可以在 Python 中使用元类做的事情,但这样做可能不一定是 Ruby 方式

那么,元类到底什么?嗯,它们是类的类。那么,让我们退后一步:究竟什么是

上课……

  • 是对象的工厂
  • 定义对象的行为
  • 在形而上学的层面上定义成为类的实例意味着什么

例如,Array该类生成数组对象,定义数组的行为并定义“数组性”的含义。

回到元类。

元类…

  • 是类的工厂
  • 定义类的行为
  • 在形而上学的层面上定义成为一个类意味着什么

在 Ruby 中,这三个职责分布在三个不同的地方:

  • 该类Class创建类并定义了一些行为
  • 单个类的 eigenclass 定义了该类的一些行为
  • “类”的概念被硬连线到解释器中,它也实现了大部分行为(例如,你不能继承自Class创建一种以不同方式查找方法的新类,或者类似的东西——方法查找算法硬连线到解释器中)

所以,这三样东西一起扮演了元类的角色,但它们都不是元类(每个只实现了元类功能的一小部分),它们的总和也不是元类(因为它们做的远不止那)。

不幸的是,有些人将类的特征类称为元类。(直到最近,我还是那些被误导的灵魂之一,直到我终于看到了曙光。)其他人称所有特征类为元类。(不幸的是,其中一个人是关于 Ruby 元编程和 Ruby 对象模型的最流行教程之一的作者。)一些流行的库添加了一个metaclass方法来Object返回对象的特征类(例如 ActiveSupport、Facets、metaid)。有些人将所有虚拟类(即特征类和包含类)称为元类。有些人称之为Class元类。即使在 Ruby 源代码本身中,“元类”这个词也被用来指代不是元类的东西。

于 2010-04-20T20:02:48.763 回答
13

您更新的问题现在看起来完全不同。如果我理解正确的话,你想加入对象分配和初始化,这与元类完全无关。(但你仍然没有写出你真正想做的事情,所以我可能还是会离开。)

在一些面向对象的语言中,对象是由构造函数创建的。但是,Ruby 没有构造函数。构造函数只是工厂方法(有愚蠢的限制);如果您可以使用(更强大的)工厂方法来代替,则没有理由将它们使用精心设计的语言。

Ruby 中的对象构造是这样工作的:对象构造分为两个阶段,分配初始化。分配是由一个名为 的公共类方法完成的allocate,该方法被定义为类的实例方法,Class通常永远不会被覆盖。(实际上,我认为您实际上无法覆盖它。)它只是为对象分配内存空间并设置一些指针,但是,此时对象并不是真正可用的。

这就是初始化器的用武之地:它是一个名为 的实例方法initialize,它设置对象的内部状态并将其带入一个一致的、完全定义的状态,可以被其他对象使用。

因此,为了完全创建一个新对象,您需要做的是:

x = X.allocate
x.initialize

[注意:Objective-C 程序员可能会认识到这一点。]

但是,因为很容易忘记调用initialize,并且作为一般规则,对象在构造后应该是完全有效的,所以有一个方便的工厂方法称为Class#new,它为您完成所有工作,看起来像这样:

class Class
  def new(*args, &block)
    obj = allocate
    obj.initialize(*args, &block)

    return obj
  end
end

[注意:实际上,initialize是私有的,因此必须使用反射来规避这样的访问限制:obj.send(:initialize, *args, &block)]

顺便说一句,这就是为什么要构造一个对象时调用公共类方法Foo.new实现私有实例方法的原因Foo#initialize,这似乎使很多新手绊倒。

但是,这些都没有以任何方式融入语言。通常调用任何类的主要工厂方法这一事实new只是一种约定(有时我希望它不同,因为它看起来类似于 Java 中的构造函数,但完全不同)。在其他语言中,构造函数必须具有特定名称。在 Java 中,它必须与类同名,这意味着 a) 只能有一个构造函数 b) 匿名类不能有构造函数,因为它们没有名称。在 Python 中,必须调用工厂方法__new__,这又意味着只能有一个。(在 Java 和 Python 中,您当然可以有不同的工厂方法,但调用它们看起来与调用默认方法不同,而在 Ruby(以及该模式起源的 Smalltalk 中)看起来完全一样。)

在 Ruby 中,可以有任意多的工厂方法,可以使用任何您喜欢的名称,并且工厂方法可以有许多不同的名称。(例如,对于集合类,工厂方法通常别名为[],它允许您编写List[1, 2, 3]而不是List.new(1, 2, 3)末端看起来更像一个数组,从而强调列表的集合性质。)

简而言之:

  • 标准化的工厂方法是Foo.new,但它可以是任何东西
  • Foo.newallocate为空对象分配内存的调用foo
  • Foo.new然后调用foo.initialize,即Foo#initialize实例方法
  • 所有这三个方法都和其他方法一样,您可以取消定义、重新定义、覆盖、包装、别名等等
  • 好吧,除了allocate需要在 Ruby 运行时内分配内存,而你不能从 Ruby 中真正做到这一点

在 Python 中,__new__大致对应于和 Ruby 中的两者new,而allocate在 Ruby 中则__init__ 完全对应initialize。主要区别在于,在 Ruby 中new调用initialize,而在 Python 中,运行时自动调用__init__after __new__

例如,这是一个最多只允许创建 2 个实例的类:

class Foo
  def self.new(*args, &block)
    @instances ||= 0
    raise 'Too many instances!' if @instances >= 2

    obj = allocate
    obj.send(:initialize, *args, &block)

    @instances += 1

    return obj
  end

  attr_reader :name

  def initialize(name)
    @name = name
  end
end

one = Foo.new('#1')
two = Foo.new('#2')
puts two.name         # => #2
three = Foo.new('#3') # => RuntimeError: Too many instances!
于 2010-04-20T22:29:16.110 回答