本页使用了标题或全文手工转换

元类

维基百科,自由的百科全书
跳到导航 跳到搜索

面向对象程序设计中,元类(英語:metaclass)是一种实例是的类。普通的类定义的是特定对象的行为,元类定义的则是特定的类及其实例的行为。不是所有面向对象编程语言都支持元类。在它能做的事情之中,元类可以覆写任何给定方面类行为的程度是不同的。元类可以通过使类成为第一类对象来实现,在这种情况下元类简单的就是构造类的一个对象。每个语言都有它自己的元对象协议,给出对象、类和元类如何交互的规则[1]

在Smalltalk-80中[编辑]

Smalltalk-80元类层级,使用UML图表示
Smalltalk中在类和元类之间的继承和实例联系的示意图

Smalltalk中,所有东西都是对象。此外,Smalltalk是基于类的系统,这意味着所有对象都有定义这个对象结构(比如说这个类拥有实例变量)和这个对象理解的消息的一个类。二者在一起蕴含了在Smalltalk中,类是一个对象,因此类也需要是一个类(叫作元类)的实例。

实例联系[编辑]

类方法实际上属于元类,就像实例方法实际上属于类一样。在早期的Smalltalks中,只有叫作Class的一个元类。这蕴含着所有类都有的方法是相同的,特别是建立一个对象的方法,也就是new。要允许类拥有它们自己方法和它们自己的实例变量(叫作类实例变量而不应混淆于类变量英语Class variable),Smalltalk-80为每个类C介入了它们自己的元类C class。这意味着每个元类在效果上都是单例类。

作为一个例子,轿车对象c是类Car的实例。依次,类Car也是个对象并且是叫作Car classCar的元类的实例。注意在元类名字中的空白。元类的名字是Smalltalk表达式,在被求值的时候,结果为元类对象。因此求值Car class,结果为Car的元类对象,它的名字是Car class(可以通过求值Car class name来确认,它返回Car的元类的名字。)

就像连体双胞胎,类和元类是共生的。元类有一个实例变量thisClass,它指向它结合的类。注意平常的Smalltalk 类浏览器英语class browser不将元类展示为单独的类。类浏览器转而允许一起同时编辑类和它的元类。

因为没有让元类相互之间表现得不同的要求,所有元类都是叫作Metaclass的一个类的实例。Metaclass的元类叫作Metaclass class,它再次是类Metaclass的实例。

继承联系[编辑]

在Smalltalk-80中,所有的(除了Object之外)都有一个超类。在一个消息给发送到对象2的时候,方法的查找开始于Integer。如果没有找到则在上行超类链,停止于Object而不管找到与否。

当一个消息给发送到Integer的时候,方法查找开始于Integer class,并上行超类链至Object class。直至Object class之前,元类的超类层级并行于类的超类层级,元类所继承的元类,就是元类对应的类所继承的类的元类。

元类链比类链有进一步的扩展,因为Object classClass的子类:Object class superclass == Class. 因此,所有元类都是Class的子类,它是所有元类的抽象超类,它描述类的一般性质。

在元类层级中类的名字容易混淆于同名的概念。四个类提供描述新类的设施,它们的继承层级(来自Object),和它们提供的主要设施:

Object是基础类,为所有对象提供公共的方法,即公共于所有对象的缺省行为,比如类访问;这里的对象是一个整数、一个组件、或一台车等等。
Behavior编译方法和创建/运行对象用到的最小状态英语State (computer science)
ClassDescription抽象类,提供类/变量命名、注释。
Class是元类的基础,它为所有类提供公共的方法(尽管它自身不是元类),是给超类的更综合性的设施;这里的类是像Integer、或WidgetCar等等这样的东西。
Metaclass为所有元类提供公共的方法,初始化类变量,实例创建消息。

例子[编辑]

下列示意图展示从Smalltalk-80派生的SqueakPharo的样例代码的结构[2]。这个结构由两个部份构成,用户部份有四个显式的对象和类及其两个隐式的元类:终端对象uv,它们连接到的类AB,它们两个连接到的右侧灰色节点表示的隐式的元类,其他的对象都是内建部份。绿色连接展示继承的子→父关系(具有隐含的朝上方向),蓝色连接展示补全的实例的成员→容器关系,从x指向x的最小实际容器的蓝色连接,是在调用在x上的方法时查找方法的起点。

 
"Pharo 1.3 / Squeak 4.2"

 r := ProtoObject.
 c := Class.
mc := Metaclass.
Object subclass: #A.
A      subclass: #B.
u := B new.
v := B new.
Implicit metaclasses in Smalltalk-80 - A sample structure

在Python中[编辑]

Python中,内建的类type是元类[3][4][5]

 
r = object
c = type
class M(c): pass

class A(metaclass=M): pass

class B(A): pass

b = B()
Eigenclass-model-class-map.svg

类的定义,不包括它的实例对象的细节,如它们的字节为单位的大小,它们在内存中的二进制格局,它们是如何分配的,每次建立实例的时候自动调用__init__方法,诸如此类。这些细节起到作用不只是在建立新实例对象的时候,而是还在每次访问实例对象的任何特性的时候。在没有元类的语言中,这些细节是在语言规定中定义的并且不能被覆写(override)。

在Python中,元类type控制着类行为的这些细节,默认定义出的类自身都是type的实例。新的元类可以很容易定义为type的子类从而覆写它,通过向类定义提供“关键字参数”metaclass就可以使用这个新的元类。

>>> type(b)
<class '__main__.B'>
>>> print(type(B), B.__bases__, [*B.__dict__])
<class '__main__.M'> (<class '__main__.A'>,) ['__module__', '__doc__']
>>> print(type(A), A.__bases__, [*A.__dict__])
<class '__main__.M'> (<class 'object'>,) ['__module__', '__dict__', '__weakref__', '__doc__']
>>> print(type(M), M.__bases__, [*M.__dict__])
<class 'type'> (<class 'type'>,) ['__module__', '__doc__']
>>> print(type(c), c.__bases__)
<class 'type'> (<class 'object'>,)
>>> print(type(r), r.__bases__)
<class 'type'> ()
>>> sorted({*r.__dict__} & {*c.__dict__})
['__delattr__', '__dir__', '__doc__', '__getattribute__', '__init__', '__new__', '__repr__', '__setattr__', '__sizeof__']
>>> sorted({*r.__dict__} - {*c.__dict__})
['__class__', '__eq__', '__format__', '__ge__', '__gt__', '__hash__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__str__', '__subclasshook__']
>>> sorted({*c.__dict__} - {*r.__dict__})
['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__dict__', '__dictoffset__', '__flags__', '__instancecheck__', '__itemsize__', '__module__', '__mro__', '__name__', '__prepare__', '__qualname__', '__subclasscheck__', '__subclasses__', '__text_signature__', '__weakrefoffset__', 'mro']

例子[编辑]

考虑下面这个最简单的Python类:

class Car:
    def __init__(self, *args, **kwargs):
        self.__dict__.update(kwargs)
    def __call__(self, **kwargs):
        self.__dict__.update(kwargs)
    @property
    def description(self):
        """返回这辆车的描述."""
        return " ".join(str(value) for value in self.__dict__.values())
>>> new_car = Car(make='Toyota', model='Prius', year=2005, engine='Hybrid')
>>> new_car(color='Green')
>>> new_car.description
'Toyota Prius 2005 Hybrid Green'

上面的例子包含了一些代码来处理初始化特性,也可以使用元类来完成这种任务:

class AttributeInitType(type):
    def __new__(*args, **kwargs):
        """返回创建的实例类."""
        cls = type.__new__(*args, **kwargs)
        def call(self, **kwargs):
            self.__dict__.update(kwargs)
        cls.__call__ = call    # 为实例类增加__call__方法
        return cls
    def __call__(cls, *args, **kwargs):
        """返回为实例类创建的实例对象."""
        obj = type.__call__(cls, *args)    # 以正常缺省方式建立实例对象。
        obj.__dict__.update(**kwargs)    # 在这个新对象上设置属性。
        return obj

这个元类只覆写实例类和对象创建部份功能。元类行为的所有其他方面仍由type处理。现在可以重写类Car使用这个新元类:

class Car(object, metaclass=AttributeInitType):
    def __init__(self, *args): pass # 接收未预期的位置实际参数
    @property
    def description(self):
        """返回这辆车的描述."""
        return " ".join(str(value) for value in self.__dict__.values())

在Ruby中[编辑]

Ruby通过介入单例(singleton)类(或自称为eigenclass)提炼了Smalltalk-80的元类概念,去除了Metaclass类,并重新定义了class-of映射。变更可以图示如下[6]

Smalltalk-80
隐式
元类
  
终端
对象
Ruby
类的
单例类
单例类的
单例类
终端
对象
终端对象的
单例类

注意在Smalltalk的隐含的元类和Ruby类的单例类之间的特定对应。Ruby的单例类模型使得隐式元类概念完全统一:所有对象x,都有叫作x的单例类的,自己的元对象,它比x高一个元层级。高阶单例类通常是纯粹概念上的存在,在大多数Ruby程序中它们不包含任何方法也不存储任何(其他)数据[7]

下面的示意图展示Ruby样例代码的核心结构[2]。这里的灰色节点表示单例类,分别对应Smalltalk-80中的元类。

 
#Ruby 1.9

r = BasicObject
c = Class
class A; end
class B < A; end
u = B.new
v = B.new

class << A; end
class << v; end
Eigenclasses in Ruby - A sample structure

图示还展示了Ruby中单例类的惰性求值v对象可以有它的单例类,作为向v增加“单例方法”的结果而被求值(被分配)。

在Objective-C中[编辑]

在Objective-C中在类和元类之间的继承和实例联系的示意图。注意Objective-C有多个根类,每个根类都有独立的层级。这个示意图只展示了例子根类NSObject的层级。每个其他根类都有类似的层级。

Objective-C中的元类几乎同于Smalltalk-80的元类,这是因为Objective-C从Smalltalk引进了很多东西。就像Smalltalk,在Objective-C中实例变量和方法是对象的类定义的。类也是对象,因此它是元类的一个实例。

就像Smalltalk,在Objective-C中类方法简单的是在类对象上调用的方法,因此一个类的类方法必须定义为在它的元类中的实例方法。因为不同的类有不同的类方法集合,每个类都必须有它自己单独的元类。类和元类总是成对创建:运行时系统拥有函数objc_allocateClassPair()objc_registerClassPair()来分别的创建和注册类-元类对。

元类没有名字,但是可以通过泛化类型Class,来引用到任何类对象的指针(类似于用于任何对象的指针的类型id)。

因为类方法是通过继承而来的,就像Smalltalk,元类必须服从并行于类继承的继承方案(比如说如果类A的父类是类B,则A的元类的父类是B的元类),除了根类的元类之外。

不同于Smalltalk,根类的元类继承自根类(通常为使用Cocoa框架的NSObject)自身。这确保了所有的类对象最终都是根类的实例,所以人们可以使用根类的实例方法,通常是在类对象自身上的,有用的针对对象的实用方法。

因为元类没有表现得不同(人们不能为元类增加类方法,所以元类对象都有相同的方法),它们都是相同的类即根类的元类的实例(不同于Smalltalk)。因此,根类的元类是自身的实例。其原因是所有元类都继承自根类,因此它们必须继承根类的类方法[8]

在语言和工具中的支持[编辑]

下面是支持元类的一些最显著的编程语言

资源描述框架(RDF)和统一建模语言(UML)二者都支持元类。

另见[编辑]

引用[编辑]

  1. ^ Ira R. Forman and Scott Danforth. Putting Metaclasses to Work. 1999. ISBN 0-201-43305-2. 
  2. ^ 2.0 2.1 Object Membership: The Core Structure of Object Technology. 
  3. ^ IBM Metaclass programming in Python, parts 1页面存档备份,存于互联网档案馆), 2页面存档备份,存于互联网档案馆) and 3页面存档备份,存于互联网档案馆
  4. ^ Artima Forum: Metaclasses in Python 3.0 (part 1 of 2)页面存档备份,存于互联网档案馆(part 2 of 2)页面存档备份,存于互联网档案馆
  5. ^ David Mertz. A Primer on Python Metaclass Programming. ONLamp. [June 28, 2006]. (原始内容存档于2003-04-30). 
  6. ^ The Ruby Object Model: Comparison with Smalltalk-80. 
  7. ^ Paolo Perrotta. Metaprogramming Ruby. Pragmatic Bookshelf. 2010. ISBN 978-1-934356-47-0. 
  8. ^ Cocoa with Love: What is a meta-class in Objective-C?
  9. ^ Herb Sutter. Metaclasses (PDF). [2020-09-25]. (原始内容存档 (PDF)于2020-11-11).