热门问题
时间线
聊天
视角

元类

来自维基百科,自由的百科全书

Remove ads

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

Smalltalk-80元类

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

元类在Smalltalk-80系统中的主要角色,是提供协议来初始化类变量,和建立元类的唯一实例(也就是其对应的类)的初始化实例。

实例联系

为了允许类拥有它们自己的方法,和叫作类实例变量它们自己的实例变量英语Instance variable,Smalltalk-80为每个类C介入了它们自己的元类C class。就像实例方法实际上属于类一样,类方法实际上属于元类。在类中定义实例变量英语Instance variable类变量英语Class variable,而在元类中定义类实例变量。

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

要得到一个实例的类,需要向它发送消息调用class方法。类和元类继承了其超类的name方法,它返回接收者名字的字符串。例如,轿车对象c是类Car的实例,则c class返回Car类对象,而c class name返回'Car';依次类推,Car class返回Car的元类对象,而Car class name返回依赖于实现,有的是nil,即没有名字,有的是'Car class',即用空格分隔的类名字和'class'

在早期的Smalltalk-76中,创建新类的方式是向Class类发送new消息[2][3]。在Smalltalk-80中,Class是元类的基础类,它是类而不是元类。所有元类都是一个Metaclass类的实例。Metaclass类是Metaclass class的实例,而Metaclass class作为元类,也是Metaclass类的实例。

Remove ads

继承联系

在Smalltalk-80中,终端对象是一个整数、一个组件、或一台车等,而类是像Integer、或WidgetCar等这样的东西,除了Object之外,所有的都有一个超类。元类所继承的元类,就是元类对应的类所继承的类的元类。

在一个消息被发送到对象的时候,方法的查找开始于它的类。如果没有找到则在上行超类链,停止于Object而不管找到与否。在一个消息被发送到一个类的时候,类方法查找开始于它的元类,并上行超类链至Object class。直至Object class,元类的超类层级并行于类的超类层级。在Smalltalk-80中,Object classClass的子类:

Object class superclass == Class.

类方法的查找在元类链之后仍可继续下去,所有元类都是Class的在继承层级中的子类,它是所有元类的抽象超类,它描述这些类的一般性质,继而最终可上溯至Object

继承层级

四个类提供描述新类的设施,下面是它们的继承层级(起自Object),和它们提供的主要设施:

  • Object,对象类是所有类的基础类,它为所有对象提供公共的方法,即公共的缺省行为。至少包括了:测试对象的功能比如class方法,比较对象,对象复制,访问对象的各部分,打印和存储对象,错误处理。
    • Behavior,行为类定义了拥有实例的对象所需要的最小状态英语State (computer science)Behavior提供建立一个类的实例的new方法,包括了一个类层级连接(superclass:),一个方法字典(methodDictionary:addSelector:withMethod:等),和对实例的描述(allInstancesinstVarNames等)。Behavior还为Smalltalk解释器提供到编译器的基本接口来编译方法源代码(compile:等)。尽管一个类的多数设施都规定在Behavior中,但很多消息不能于此实现,对类的完全描述转而在它的子类之中提供。
      • ClassDescription,类描述类为ClassMetactass提供了共同的基础类。它表现为:类命名(name)、类注释(commentcomment:)、和命名实例变量(addlnstVarName:等)。特别是,它增加了结构来组织在方法字典中方法(compile:classified:)和类自身(category:)。ClassDescription还提供了在外部流(文件)上存储完全的类描述的机制,和记述对类描述的变更的机制。
        • Metaclass,元类类是创建元类的类,它为所有元类提供公共的方法[4]Metaclass增加了关键性消息,一个是发送到Metaclass自身的消息subclassOf: superMeta,用来创建元类superMeta的一个子类;另一个是发送到Metaclass某个实例的消息,用来建立这个元类的唯一实例,并对这个类进行完全的初始化,它的一系列参数中针对类和超类名字的参数是:name:……subclassOf:……
        • Class,类类是所有元类的基础类,它为所有类提供公共的方法。Class提供比ClassDescription更具描述性的设施,尤其是增加了对类变量名字(addClassVarName:等)和共享的池变量(addSharedPool:等)的表示,并定义了预期在子类中覆写的方法initialize,即在需要时定义在元类中的类方法initialize初始化类变量。Class还提供比Behavior更综合性的编程支持设施,比如创建一个类的子类的消息:subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
Remove ads

例子

下列例子展示,从Smalltalk-80派生的SqueakPharo的样例代码的结构[5],它们的继承层级的根类实际上是ProtoObjectProtoObject封装了所有对象都必须拥有的极小化的消息集合,它被设计为引发尽可能多的错误,用来支持代理(proxy)定义[6]。例如Smalltalk-80的Object中,错误处理消息doesNotUnderstand:,和系统原始消息become:,就转而在ProtoObject中定义了。

在示意图中,纵向的绿色连接,展示继承联系的“子→父”关系(隐含的自下而上),横向的蓝色连接展示实例联系的“成员→容器”关系,从x出的发蓝色连接,指向x的最小实际容器,它是在调用在x上的方法时查找方法的继承链起点:

 
 r := ProtoObject.
 c := Class.
mc := Metaclass.
Object subclass: #A.
A      subclass: #B.
u := B new.
v := B new.
Thumb

这个结构由两个部分构成,用户部分有四个显式的对象和类及其两个隐式的元类:终端对象uv,它们连接到的类AB,它们两个连接到的右侧灰色节点表示的隐式的元类,其他的对象都是内置部分。

Remove ads

方法查找次序

下面是方法查找次序的辨析:

  • 每个终端对象,在查找方法时,都首先查找自己的类;然后按类继承链上溯,最终直接上溯至Object(对象类),而不经过Class(类类)、Metaclass(元类类)、ClassDescription(类描述类)和Behavior(行为类)。
  • 每个类,包括ObjectClassMetaclass,在查找查找方法时,首先查找自己的元类;然后按元类继承链上溯,最终经过Object class(对象元类)而上溯至Class;接着按类继承链上溯ClassDescription(类描述类),最终经过Behavior(行为类)上溯至Object(对象类)。
  • 每个元类,包括Metaclass class,在查找方法时,因为都是Metaclass的实例,所以首先查找Metaclass;然后按类继承链上溯ClassDescription(类描述类),最终经过Behavior(行为类)上溯至Object(对象类)。
Remove ads

类图示意

下面是两个示意图,二者都是纵向连线表示实例联系,而横向连线表示继承联系。实例联系以Metaclass(元类类)及其元类为顶端,而继承联系以Object(对象类)及其元类为中心,其中Object class(对象元类)继承Class(类类)是串接元类继承链与类继承链的关键环节。前者图示将Metaclass及其元类放置在最上方的独立两行,使得实例联系尽量成为树状向上汇聚;后者图示将Metaclass及其元类放置在最左边,使得继承联系尽量都在同一行之上。

Remove ads

Objective-C元类

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)。这确保了所有的元类最终都是根类的子类,从而人们可以将根类的实例方法,它们通常是针对对象有用的实用方法,使用于类对象自身上。

Remove ads

Python元类

Python中,内置的类type是元类[7][8]。Python中的元类type,在其专有功能上涉及到Smalltalk-80中的类描述类(ClassDescription)、元类类(Metaclass)和类类(Class)。

概述

Python与Smalltalk-80在对象系统上最显著区别,是Python中类方法的创建由内置的类方法修饰器classmethod统一处理,经过修饰的类方法与实例方法以同样的方式存储在类中。由于不需要为每个类建立自己的元类来持有它的类方法,也就无需再特设Smalltalk-80中的元类类Metaclass。每个类在创建自身子类时查找构造方法最终上溯到共同的一个类,它在Smalltalk-80中是类类Class,而在Python中就是类型类type

在Python中可以创建继承自缺省元类type的定制元类,用户可以通过在类定义中提供关键字参数metaclass来使用这种定制元类。例如:

 
r = object
c = type

class M(c): pass
class A(metaclass=M): pass
class B(A): pass

b = B()
Thumb

下面是对例子所建立的类和对象的内省:

>>> def view(cls):
...     print(type(cls).__name__,  
...         [x.__name__ for x in cls.__bases__])
... 
>>> def inspect(cls):
...     print(type(cls).__name__,  
...         [x.__name__ for x in cls.__bases__],  
...         sorted([*cls.__dict__]))
... 
>>> print(type(b).__name__, ' '*5, [*b.__dict__])
B       []
>>> inspect(B)
M ['A'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__']
>>> assert [dir(b)] == [dir(B)]
>>> inspect(A)
M ['object'] ['__dict__', '__doc__', '__firstlineno__', '__module__', '__static_attributes__', '__weakref__']
>>> [x.__name__ for x in A.__subclasses__()]
['B']
>>> inspect(M)
type ['type'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__']
>>> view(type)
type ['object']
>>> view(object)
type []
>>> assert B.__instancecheck__(b) is True
>>> assert M.__instancecheck__(M, B) is True
>>> assert type.__instancecheck__(type, M) is True
>>> assert A.__subclasscheck__(B) is True
>>> assert object.__subclasscheck__(type) is True
>>> assert type.__subclasscheck__(type, M) is True
>>> sorted({*object.__dict__} - {*type.__dict__})
['__class__', '__eq__', '__format__', '__ge__', '__getstate__', '__gt__', '__hash__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__str__', '__subclasshook__']
>>> sorted({*object.__dict__} & {*type.__dict__})
['__delattr__', '__dir__', '__doc__', '__getattribute__', '__init__', '__new__', '__repr__', '__setattr__', '__sizeof__']
>>> sorted({*type.__dict__} - {*object.__dict__})
['__abstractmethods__', '__annotations__', '__base__', '__bases__', '__basicsize__', '__call__', '__dict__', '__dictoffset__', '__flags__', '__instancecheck__', '__itemsize__', '__module__', '__mro__', '__name__', '__or__', '__prepare__', '__qualname__', '__ror__', '__subclasscheck__', '__subclasses__', '__text_signature__', '__type_params__', '__weakrefoffset__', 'mro']

下面是承接上例的对数值类型和抽象基类内省:

>>> view(int)
type ['object']
>>> import numbers
>>> view(numbers.Integral)
ABCMeta ['Rational']
>>> [x.__name__ for x in numbers.Integral.__mro__]
['Integral', 'Rational', 'Real', 'Complex', 'Number', 'object']
>>> assert numbers.Integral.__subclasscheck__(int) is True
>>> assert numbers.Integral.__subclasshook__(int) is NotImplemented
>>> sorted({*numbers.Integral.__dict__} - {*int.__dict__})
['__abstractmethods__', '__firstlineno__', '__module__', '__slots__', '__static_attributes__', '_abc_impl']
>>> class MyInt(int): pass
... 
>>> numbers.Integral.register(MyInt)
<class '__main__.MyInt'>
>>> assert numbers.Integral.__subclasscheck__(MyInt) is True
>>> assert numbers.Integral.__instancecheck__(MyInt()) is True
>>> from abc import ABC, ABCMeta
>>> assert ABC.__subclasscheck__(numbers.Integral) is False
>>> view(ABC)
ABCMeta ['object']
>>> sorted({*ABC.__dict__} - {*A.__dict__})
['__abstractmethods__', '__slots__', '_abc_impl']
>>> ABC.__abstractmethods__
frozenset()
>>> assert type.__instancecheck__(ABCMeta, numbers.Integral) is True
>>> view(ABCMeta)
type ['type']
>>> assert {*ABCMeta.__dict__} > {*M.__dict__}
>>> sorted({*ABCMeta.__dict__} - {*M.__dict__})
['__instancecheck__', '__new__', '__subclasscheck__', '_abc_caches_clear', '_abc_registry_clear', '_dump_registry', 'register']
>>> [x.__module__+'.'+x.__qualname__ for x in type.__subclasses__(type)]
['abc.ABCMeta', 'enum.EnumType', 'ast._ABC', 'typing._AnyMeta', 'typing.NamedTupleMeta', 'typing._TypedDictMeta', '__main__.M']

在Python的数据模型中:

  • object字典减除type字典的差集中的方法特性含摄:
    • 标准类型层级英语Class hierarchy中的类实例的特殊特性__class__[9]
    • 对象特殊方法中的基本定制(customization)方法的字符串表示、格式化字符串表示、丰富比较和散列运算方法[10],和定制化(customizing)类创建的类方法__init_subclass__()的缺省实现[11]
    • 标准库的抽象基类(ABC)模块中的ABCMeta元类所创建诸类的方法__subclasshook__()的缺省实现[12],和数据持久化模块中的做对象序列化的封存(pickling)类实例方法的缺省实现[13]
  • object字典type字典的交集中的方法特性含摄:
    • 对象特殊方法中基本定制方法__init__()__new__()__repr__()[10],和定制化特性访问方法__getattribute__()__setattr__()__delattr__()__dir__()[14]
    • 标准类型层级英语Class hierarchy中的诸对象的特殊特性__doc__,以及由sys.getsizeof()调用的对象特殊方法__sizeof__()
  • type字典减除object字典的差集中的方法特性主要含摄:
    • 标准类型层级英语Class hierarchy中的做定制(custom)类的特殊特性__name____bases____mro____dict__和特殊方法__subclasses__()[15]
    • 对象特殊方法中的模拟可调用对象方法__call__()[16],和定制化实例与子类检查特殊方法__instancecheck__()__subclasscheck__()[17]
    • 对象特殊方法中的定制化类创建的方法__prepare__()的缺省实现[18],和数据描述器__abstractmethods__,这个特殊属性由标准库的抽象基类(ABC)模块中的ABCMeta元类在__new__()方法中为其实例进行设置[19],并可用函数update_abstractmethods()来更新[12]

在Python中,类型和方法可以在运行时动态创建[20]

Remove ads

静态方法与类方法

Python中的静态方法类方法修饰器,基于了非数据描述器协议[21],下面是静态方法描述器的纯Python等价者:

import functools

class StaticMethod():
    "模拟cpython/Objects/funcobject.c中的PyStaticMethod_Type()"

    def __init__(self, f):
        self.f = f
        functools.update_wrapper(self, f)

    def __get__(self, obj, objtype=None):
        return self.f

    def __call__(self, *args, **kwds):
        return self.f(*args, **kwds)

这里的__get__(self, obj)直接返回所包装的函数f,而__call__(self, *args, **kwds)以指定实际参数执行所包装的函数f

下面是类方法描述器的纯Python等价者:

import functools

class ClassMethod():
    "模拟cpython/Objects/funcobject.c中的PyClassMethod_Type()"

    def __init__(self, f):
        self.f = f
        functools.update_wrapper(self, f)

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        return MethodType(self.f, cls)

这里的__get__(self, obj)通过方法类型MethodType,在从类或从实例发起调用而有传入的类引用cls的常见情况下,建立将函数f绑定到了cls上的方法对象;在没有传入的类引用的情况下,建立将函数f绑定到了type(obj)上的方法对象。

Remove ads

定制元类例子

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

class Car():
    descr = None
    def __init__(self, *args, **kwargs):
        self.descr = kwargs
    def __call__(self, *args, **kwargs):
        self.descr |= kwargs
    @property
    def description(self):
        """返回这辆车的描述。"""
        return " ".join(str(value) for value in self.descr.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 AttrInitType(type):
    def __init__(cls, *args, **kwargs):
        """初始化新建的类。"""
        #为新建的类增加类特性。
        cls.descr = None  
    def __call__(cls, *args, **kwargs):
        """返回为所建的类新建的对象。"""
        #截留给类构造子的关键字参数,以缺省方式建立对象。
        obj = type.__call__(cls, *args) 
        #在对象初始化之后,按关键字参数在对象上设置特性。
        obj.descr = kwargs
        return obj

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

class Car(object, metaclass=AttrInitType):
    def __call__(self, *args, **kwargs):
        self.descr |= kwargs
    @property
    def description(self):
        """返回这辆车的描述。"""
        return " ".join(str(value) for value in self.descr.values())

Ruby元类

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

Smalltalk-80
隐式
元类
终端
对象
Ruby
类的
本征类
本征类的
本征类
终端
对象
终端对象的
本征类

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

下面的示意图展示Ruby样例代码的核心结构[24]。这里的灰色节点表示打开Av本征类后扩张出来的本征类。

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

class << A; end
class << v; end
Thumb

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

在语言和工具中的支持

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

一些不甚广泛传播的语言支持元类,包括OpenJava英语OpenJava、OpenC++、OpenAda、CorbaScript英语CorbaScript、ObjVLisp、Object-Z英语Object-Z、MODEL-K、XOTcl英语XOTcl和MELDC。其中几种语言可追溯日期至1990年代早期并具有学术价值[26]

Logtalk英语LogtalkProlog的面向对象扩展,它也支持元类。

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

另见

引用

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads