2

动态语言中的类是如何实现的?

我知道 Javascript 正在使用原型模式(“某处”有一个未绑定 JS 函数的容器,当通过对象调用它们时它们是绑定的),但我不知道它在其他语言中是如何工作的。

我对此感到好奇,因为我想不出一种有效的方法来拥有本机绑定方法,而不会通过复制每个实例的成员来浪费内存和/或 cpu。

(通过绑定方法,我的意思是以下代码应该可以工作:)

class Foo { function bar() : return 42; };

var test = new Foo();
var method = test.bar;
method() == 42;
4

1 回答 1

1

这在很大程度上取决于语言实现。我会告诉你我对 CPython 和 PyPy 的了解。

一般的想法,这也是 CPython 在大多数情况下所做的,如下所示:

  1. 每个对象都有一个类,特别是对该类对象的引用。
  2. 除了显然存储在单个对象中的实例成员之外,该类还具有成员。这包括方法,因此方法没有每个对象的成本。
  3. 一个类具有由继承关系确定的方法解析顺序 (MRO),其中每个基类只出现一次。如果我们没有多重继承,这只是对基类的引用,但这样一来,MRO 就很难即时计算出来(每次都必须从最派生的类开始)。
  4. (类也是对象,它们本身也有类,但我们暂时忽略这一点。)
  5. 如果对象的属性查找失败,则按照 MRO 指定的顺序在 MRO 中的类上查找相同的属性。(这是默认行为,可以通过定义 和 等魔术方法来更改__getattr____getattribute__

到目前为止如此简单,并不能真正解释绑定方法。我只是想确保我们谈论的是同一件事。缺少的部分是描述符。描述符协议在语言参考的“深奥魔法”部分中定义,但简短而简单的故事是,对类的查找可以被它通过方法产生的对象__get__劫持。更重要的是,该__get__方法被告知查找是在实例上还是在“所有者”(类)上开始的。

在 Python 2 中,我们有一个丑陋且不必要的UnboundMethod描述符(除了__get__方法之外),它只是简单地包装函数以在Class.method(self)如果self不是可接受的类型时抛出错误。在 Python 3 中,__get__它只是所有函数对象的一部分,并且没有绑定方法。在这两种情况下,__get__当您在类上查找该方法时,该方法会返回自身(因此您可以使用Class.method,这在少数情况下很有用),当您在对象上查找它时,它会返回一个“绑定方法”对象。这个绑定的方法对象只是存储原始函数和实例,并将后者作为第一个参数传递给前者__call__(覆盖函数调用语法的特殊方法)。

因此,对于 CPython:虽然绑定方法有成本,但它比您想象的要小。在空间方面只需要两个引用,并且 CPU 成本仅限于小的内存分配,以及调用时的额外间接。请注意,此成本适用于所有方法调用,而不仅仅是那些实际使用绑定方法功能的方法调用。a.f() 必须调用描述符并使用它的返回值,因为在动态语言中,我们不知道它是否经过猴子修补来做一些不同的事情。

在 PyPy 中,事情更有趣。由于它是一种不影响正确性的实现,因此上述模型对于语义推理仍然是正确的。但是,它实际上更快。除了 JIT 编译器在大多数情况下内联然后消除上述整个混乱之外,它们还解决了字节码级别的问题。有两个新的字节码指令,它们保留了语义,但在a.f(). 还有一个方法缓存可以简化查找过程,但需要一些额外的簿记(尽管其中一些簿记已经为 JIT 完成)。

于 2012-09-24T10:59:04.683 回答