Ruby中与Python类的对应是什么?

11 投票
2 回答
1783 浏览
提问于 2025-04-15 21:49

Python 有一个叫做元类的概念,如果我理解得没错的话,它可以让你在创建一个类的对象时修改这个对象。也就是说,你并不是在修改类本身,而是在创建并初始化即将生成的对象。

Python(至少在我认为的 3.0 版本及之后)还有一个叫做类装饰器的概念。如果我理解得没错,类装饰器可以让你在声明类的时候修改类的定义。

我相信 Ruby 也有类似于类装饰器的功能,但目前我还不知道 Ruby 有没有和元类相对应的功能。我相信你可以通过一些函数来处理任何 Ruby 对象,随心所欲地修改它,但这个语言里有没有像元类那样的功能呢?

所以,Ruby 有没有类似于 Python 的元类的东西呢?

编辑 我之前对 Python 的元类理解错了。元类和类装饰器做的事情非常相似。它们都是在定义类的时候修改类,但方式不同。希望有 Python 大牛能来更好地解释这些功能。

不过,一个类或类的父类可以实现一个 __new__(cls[,..]) 函数,这个函数可以在对象初始化之前自定义对象的构造过程,接着再用 __init__(self[,..]) 来初始化。

编辑 这个问题主要是为了讨论和学习这两种语言在这些功能上的比较。我对 Python 比较熟悉,但对 Ruby 不太了解,所以有点好奇。希望其他对这两种语言有同样疑问的人能觉得这个帖子有帮助和启发。

2 个回答

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.new调用allocate为一个空对象foo分配内存
  • Foo.new然后调用foo.initialize,即Foo#initialize实例方法
  • 这三者都是普通的方法,你可以取消定义、重新定义、重写、包装、别名等等
  • 不过,allocate需要在Ruby运行时内部分配内存,这个你从Ruby中是做不到的

在Python中,__new__大致对应于Ruby中的newallocate,而__init__完全对应于Ruby中的initialize。主要的区别在于,在Ruby中,new会调用initialize,而在Python中,运行时会在__new__之后自动调用__init__

例如,这里有一个类,它只允许最多创建两个实例:

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!
24

Ruby没有元类。虽然Ruby里面有一些结构,有些人错误地称它们为元类,但其实并不是,这让很多人感到困惑。

不过,在Ruby中有很多方法可以实现你用元类能做到的事情。但如果不告诉我们你具体想做什么,就很难说出这些方法是什么。

简单来说:

  • Ruby没有元类
  • Ruby没有一个结构能对应Python的元类
  • Python用元类能做到的事情,Ruby也能做到
  • 但没有一个“单一”的结构,你需要根据具体想做的事情使用不同的结构
  • 这些结构可能还有其他功能,不一定和元类对应(虽然它们可能对应Python中的其他东西)
  • 虽然你可以在Ruby中做任何Python元类能做的事情,但可能不那么简单
  • 不过,通常会有一种更符合Ruby风格的优雅解决方案
  • 最后,虽然你可以在Ruby中做任何Python元类能做的事情,但这样做不一定是“Ruby的方式”

那么,元类到底是什么呢?其实它们是“类的类”。所以,我们先回过头来,类到底是什么呢?

类……

  • 是对象的工厂
  • 定义了对象的行为
  • 在更深层次上定义了成为这个类的实例意味着什么

举个例子,Array类生成数组对象,定义了数组的行为,并且定义了“数组性”意味着什么。

回到元类。

元类……

  • 是类的工厂
  • 定义了类的行为
  • 在更深层次上定义了成为一个类意味着什么

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

  • Class类创建类并定义了一部分行为
  • 每个类的特有类(eigenclass)定义了类的一部分行为
  • “类性”的概念是硬编码在解释器里的,解释器也实现了大部分行为(例如,你不能从Class继承来创建一种新类,使得方法查找方式不同,方法查找算法是硬编码在解释器里的)

所以,这三者共同承担了元类的角色,但它们都不是元类(每个只实现了元类的一小部分功能),而且它们的“总和”也不是元类(因为它们的功能远不止这些)。

不幸的是,有些人把类的特有类称为元类。(直到最近,我也是这些误解的人之一,直到我终于明白了。)还有些人把所有的特有类都称为元类。(不幸的是,其中一个人是关于Ruby元编程和Ruby对象模型最受欢迎的教程的作者。)一些流行的库在Object上添加了一个metaclass方法,返回对象的特有类(例如ActiveSupport、Facets、metaid)。还有人把所有的虚拟类(即特有类和包含类)都称为元类。还有人把Class称为元类。甚至在Ruby的源代码中,“元类”这个词也被用来指代一些并不是元类的东西。

撰写回答