元类

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

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

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

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

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

要得到一个实例的类,需要向它发送消息调用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消息。在Smalltalk-80中,Class是元类的基础类,它是类而不是元类。所有元类都是一个Metaclass类的实例。Metaclass类是Metaclass class的实例,而Metaclass class作为元类,也是Metaclass类的实例。

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

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

Object class superclass == Class.

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

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

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

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

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

方法查找次序
下面是方法查找次序的辨析:
*每个终端对象,在查找方法时,都首先查找自己的类;然后按类继承链上溯,最终直接上溯至Object(对象类)。
*每个类,包括Object(对象类)、Class(类类)和Metaclass(元类类),在查找查找方法时,首先查找自己的元类;然后按元类继承链上溯,最终经过Object class(对象元类)而上溯至Class(类类);接着按类继承链上溯ClassDescription(类描述类),最终经过Behavior(行为类)上溯至Object(对象类)。
*每个元类,包括Metaclass class(元类元类),在查找方法时,首先查找Metaclass(元类类);然后按类继承链上溯ClassDescription(类描述类),最终经过Behavior(行为类)上溯至Object(对象类)。

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

File:Smalltalk 80 metaclasses.svg|Smalltalk-80的类和元类层级,使用UML类图表示,这里从左至右,第一列是Class class(类元类)和Class(类类),第二列是ClassDescription class(类描述元类)和ClassDescription(类描述类),第三列是Behavior class(行为元类)和Behavior(行为类),第四列是Object class(对象元类)和Object(对象类),第五列是Metaclass class(元类元类)、Metaclass(元类类)、Car class(Car元类)、Car(Car类)和c:Car(Car实例)。

File:Smalltalk metaclass.png|Smalltalk中在类和元类之间的继承和实例联系的示意图,这里从左至右,第一列是Metaclass元类和Metaclass(元类类),第二列是Class元类和Class(类类),第三列是ClassDescription元类与Behavior元类、和ClassDescription(类描述类)与Behavior(行为类),第四列是Object元类、Object(对象类)和Object实例,第五列是Foo元类、Foo类和Foo实例,第六列是Bar元类、Bar类和Bar实例。

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

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

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

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

元类都是相同的类即根类元类的实例,而根类元类是自身的实例。因为类方法是通过继承联系来继承的,就像Smalltalk,除了根类元类之外,元类继承联系必须并行于类继承联系(比如说如果类A的父类是类B,则A的元类的父类是B的元类)。

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

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

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

下面先建立辅助函数,inspect()打印作为类的一个对象的元类、基类列表和排序后的特性与方法名字列表,review()打印作为类的一个对象的元类和基类列表,differ()求得两个对象的字典的差集,intersect()求得两个对象的字典的交集,differ_dir()求得两个对象上的__dir__()的差集,name_list()给出一个容器中的所有对象的名字列表,fullname_list()给出一个容器中的所有对象的详尽名字列表,belong()测试一个对象是否在另一个对象的字典之中:

def inspect(a):
print(type(a).__name__,
[x.__name__ for x in a.__bases__] if hasattr(a, '__bases__') else None,
sorted([*a.__dict__]) if hasattr(a, '__dict__') else None)

def review(a):
print(type(a).__name__,
[x.__name__ for x in a.__bases__] if hasattr(a, '__bases__') else None)

def differ(x, y):
return sorted({x.__dict__} - {y.__dict__})

def intersect(x, y):
return sorted({x.__dict__} & {y.__dict__})

def differ_dir(x, y):
return sorted({x.__dir__()} - {y.__dir__()})

def name_list(a):
return [x.__name__ for x in a]

def fullname_list(a):
return [x.__module__+'.'+x.__qualname__ for x in a]

def belong(x, a):
return x.__name__ in a.__dict__

再内省这个例子所建立的类对象与类实例对象及其实例方法。
**对象特殊方法中,基本定制(customization)方法:字符串表示方法__repr__(),格式化字符串表示方法__str__()和__format__(),丰富比较方法__lt__()、__le__()、__eq__()、__ne__()、__gt__()和__ge__(),散列运算方法__hash__()。
**标准库中,抽象基类(ABC)模块的ABCMeta元类所创建诸类的方法__subclasshook__()的缺省实现。
**标准库中,封存(pickle)模块的进行对象序列化的类实例方法__getstate__()、__reduce__()和__reduce_ex__()的缺省实现。

*object字典与type字典的交集中的方法或特性含摄:
**标准类型中,诸对象的特殊特性__doc__。
**对象特殊方法中,基本定制(customization)方法__init__()、__new__()和__repr__()。
**对象特殊方法中,定制化(customizing)特性访问方法__getattribute__()、__setattr__()、__delattr__()和__dir__()。
**标准库中,sys.getsizeof()调用的对象特殊方法__sizeof__()。

*type字典减除object字典的差集中的方法或特性主要含摄:
**标准类型中,自定义(custom)类的特殊特性__name__、__bases__、__mro__、__module__、__qualname__,取置描述器getset_descriptor类型的__dict__属性,以及特殊方法__subclasses__()和mro()。
**对象特殊方法中,模拟可调用对象方法__call__()的缺省实现。
**对象特殊方法中,定制化(customizing)实例与子类检查特殊方法__instancecheck__()和__subclasscheck__()。
**对象特殊方法中,定制化(customizing)类创建的方法__prepare__()的缺省实现。
**标准库中,抽象基类(ABC)模块设立的数据描述器__abstractmethods__。
在type字典之中,Python 3.10版本介入了用于联合类型的__or__(self, other)和__ror__(self, other)方法,它们是模拟数值类型的特殊方法,分别实现了二元运算|和交换了运算元的二元运算|;Python 3.12版本中增加了__type_params__特性,Python 3.14版本更改了__annotations__特性并增加了__annotate__()方法、__flags__、__weakrefoffset__、__base__、__dictoffset__,以及给标准库inspect.signature()去做分析的__text_signature__特性。

实例关系与子类关系
使用特殊方法type.__instancecheck__(self, instance)和type.__subclasscheck__(self, subclass),分别内省上述实例对象、类和元类之间的实例关系和子类关系。由于自定义元类可以覆写元类type的__instancecheck__()和__subclasscheck__()方法,这里尽量不采用元类的方法描述器转而采用它所产生的内建方法。

字典与目录
内省特殊特性__dict__,它的类型对于类实例对象就是字典dict,而对于类对象是映射代理mappingproxy:

>> import types >> assert type(b.__dict__) is dict >> assert type(B.__dict__) is types.MappingProxyType >> assert types.MappingProxyType({'x':1})['x'] == 1 >> assert B.__dict__ is not B.__dict__ == B.__dict__ >> >> assert type(A.__dict__['__dict__']) is types.GetSetDescriptorType >> assert type(type.__dict__['__dict__']) is types.GetSetDescriptorType >> inspect(types.GetSetDescriptorType)

type ['object'] ['__delete__', '__doc__', '__get__', '__name__', '__objclass__', '__qualname__', '__repr__', '__set__']
>>> inspect(types.GetSetDescriptorType.__get__)
wrapper_descriptor None None
>>>
>>> assert type(A.__dict__['__dict__'].__get__) is types.MethodWrapperType
>>> assert b.__dict__ is A.__dict__['__dict__'].__get__(b)
>>> b.__dict__['x'] = 1
>>> assert b.x == 1
>>>
>>> assert type(type.__dict__['__dict__'].__get__) is types.MethodWrapperType
>>> assert B.__dict__ == type.__dict__['__dict__'].__get__(B)
>>> assert B.f is B.__dict__['f']

类实例对象比如这里的对象b,在创建它的类B的继承链上追溯出作为object类的直接子类的基类A,它的__dict__特性是由这个基类的取置描述器类型的__dict__属性产生的;类对象比如这里的类B,它的__dict__特性是由元类type的取置描述器类型的__dict__属性产生的。

接着内省特殊方法object.__dir__()和type.__dir__()以及内建函数dir():

>> inspect(object.__dir__)

method_descriptor None None
>>> assert B.__dir__ is A.__dir__ is object.__dir__
>>> assert b.__dir__ == object.__dir__.__get__(b)
>>> assert b.__dir__() == object.__dir__(b)
>>> def object_dir(a):
... return (({*a.__dict__}
... if not hasattr(a, '__bases__') and hasattr(a, '__dict__')
... else set()) | {*type.__dir__(a.__class__)})
...
>>> assert {*object.__dir__(b)} == object_dir(b)
>>> assert {*object.__dir__(B)} \
... == {*object.__dir__(A)} \
... == {*type.__dir__(M)}
>>> assert {*object.__dir__(object)} \
... == {*object.__dir__(M)} \
... == {*object.__dir__(type)} \
... == {*type.__dir__(type)}
>>>
>>> inspect(type.__dir__)
method_descriptor None None
>>> assert M.__dir__ is type.__dir__
>>> assert {type.__dir__(B)} == {B.__dict__} | {*type.__dir__(A)}
>>> assert {type.__dir__(A)} == {A.__dict__} | {*type.__dir__(object)}
>>> assert {type.__dir__(M)} == {M.__dict__} | {*type.__dir__(type)}
>>> assert {type.__dir__(type)} == {type.__dict__} | {*type.__dir__(object)}
>>> assert {type.__dir__(object)} == {object.__dict__}
>>>
>>> assert belong(dir, __builtins__)
>>> inspect(dir)
builtin_function_or_method None None
>>> assert dir(b) == sorted(object.__dir__(b))
>>> assert dir(B) == sorted(type.__dir__(B))
>>> differ_dir(dir, type.__dir__)
['__module__', '__self__']
>>> assert dir.__self__ is __builtins__
>>> assert dir.__module__ == 'builtins'

这里内建函数的__self__和__module__特性是实例方法的特殊只读特性。

取得特性与弱引用
内省体现了动态分派机制的特殊方法object.__getattribute__()和type.__getattribute__():

>> inspect(object.__getattribute__)

wrapper_descriptor None None
>>> inspect(type.__getattribute__)
wrapper_descriptor None None
>>> assert b.__getattribute__ is B.__getattribute__ \
... is object.__getattribute__ != type.__getattribute__
>>>
>>> name_list(B.__mro__)
['B', 'A', 'object']
>>>
>>> assert b.__dict__ is object.__getattribute__(b, '__dict__')
>>> assert B.__dict__ == type.__getattribute__(B, '__dict__')
>>>
>>> assert b.__dir__ == object.__getattribute__(b, '__dir__')
>>> assert B.__dir__ is A.__dir__ is object.__dir__ \
... == type.__getattribute__(object, '__dir__')
>>> assert M.__dir__ is type.__dir__ \
... == type.__getattribute__(type, '__dir__')

解释器中所有的类实例对象和类对象的对象.特性语法糖,都相当于分别采用object.__getattribute__()和type.__getattribute__()特殊方法来取得对象的特性。__getattribute__()和__setattr__()特殊方法的示例,可参见访问者模式的Python表述代码。

一个类的__mro__特性确定了它于类层级中的所有基类的优先次序即方法决定次序(MRO),在查找特定的特性或方法之时,次序居前的基类优先于位居其后的它的父辈类或平辈类。比如有一个继承层级,类X依次多重继承了2个基类C和D即class X(C, D): pass,而类C的__mro__包含了[C, A, O]这3个基类,类D的__mro__包含了[D, B, O]这3个基类,则类X的__mro__将包含[C, A, D, B, O]这5个基类。

接着内省弱引用特性__weakref__:

>> import weakref >> inspect(A.__weakref__)

getset_descriptor None None
>>> c = weakref.ref(b)
>>> d = weakref.ref(b)
>>> assert d is c is b.__weakref__
>>> inspect(b.__weakref__)
ReferenceType None None

在一个类中的类变量__slots__,可以被赋值为变量名字序列,它为所声明的这些变量在类实例对象中保留空间,并阻止为其自动建立特性__dict__和__weakref__。

静态方法与类方法
Python中内建的静态方法类staticmethod和类方法类classmethod,基于了非数据的描述器协议:

>> assert belong(staticmethod, __builtins__) >> assert belong(classmethod, __builtins__) >> inspect(staticmethod)

type ['object'] ['__call__', '__dict__', '__doc__', '__func__', '__get__', '__init__', '__isabstractmethod__', '__new__', '__repr__', '__wrapped__']
>>> inspect(classmethod)
type ['object'] ['__dict__', '__doc__', '__func__', '__get__', '__init__', '__isabstractmethod__', '__new__', '__repr__', '__wrapped__']
>>> inspect(staticmethod.__call__)
wrapper_descriptor None None
>>> inspect(staticmethod.__get__)
wrapper_descriptor None None
>>> inspect(classmethod.__get__)
wrapper_descriptor None None

在方法调用之时,静态方法不接收隐式的第一个实际参数。下面是静态方法描述器的纯Python等价者,它模拟了cpython/Objects/funcobject.c中的PyStaticMethod_Type():

import functools

class StaticMethod():
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等价者,它模拟了cpython/Objects/funcobject.c中的PyClassMethod_Type():

import functools
from types import MethodType

class ClassMethod():
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)

在Python中,类型和方法可以在运行时动态创建,这里的__get__(self, obj)通过方法类型types.MethodType,在从类或从实例发起调用而有传入的类引用cls的常见情况下,建立将函数f绑定到了cls上的方法对象;在没有传入的类引用的特殊情况下,建立将函数f绑定到了type(obj)上的方法对象。

数据描述器
通过描述器协议可以建立数据描述器。

下面是属性描述器的纯Python等价者,它模拟了cpython/Objects/descrobject.c中的PyProperty_Type():

class Property:
__slots__ = ('fget', 'fset', 'fdel', '__doc__', '__name__')
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __set_name__(self, owner, name):
self.__name__ = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
@property
def __isabstractmethod__(self):
return any(getattr(f, '__isabstractmethod__', False)
for f in (self.fget, self.fset, self.fdel))

为了使描述器正确的互操作于抽象基类机制,描述器必须使用__isabstractmethod__属性标识自身,一般而言如果组成这个描述器的任何方法是抽象的,则这个属性应当为True:

>> from abc import ABCMeta, abstractmethod, ABC >> review(ABCMeta)

type ['type']
>>> differ(ABCMeta, M)
['__instancecheck__', '__new__', '__subclasscheck__', '_abc_caches_clear', '_abc_registry_clear', '_dump_registry', 'register']
>>> assert type.__subclasscheck__(type, ABCMeta) is True
>>> assert type.__instancecheck__(type, ABCMeta) is True
>>> review(type.__dict__['__abstractmethods__'])
getset_descriptor None
>>> inspect(abstractmethod)
function None []
>>>
>>> review(ABC)
ABCMeta ['object']
>>> differ(ABC, A)
['__abstractmethods__', '__slots__', '_abc_impl']
>>> ABC.__abstractmethods__
frozenset()
>>> class D(ABC):
... @abstractmethod
... def f(self): pass
...
>>> review(D)
ABCMeta ['ABC']
>>> assert D.f.__isabstractmethod__ is True
>>> D.__abstractmethods__
frozenset({'f'})

当指示抽象方法的修饰器abstractmethod()同其他方法描述器一起应用之时,它应当被用作最内的修饰符:
*name字符串,是新建类的名字,它会成为这个类的__name__特性。
*bases元组,包含新建类的诸基类,它会成为这个类的__bases__特性。
*dict字典,包含新建类的主体中的特性和方法定义,它在成为这个类的__dict__特性之前可以被复制或包装。
*kwargs字典,包含关键字实际参数,它会被传递给适当的元类机制即通常的__init_subclass__(),这种方式同于在类定义中附加除metaclass之外的关键字参数。

这个自定义元类也可以写为:

class AttrInitType(type):
def __new__(mcls, name, bases, dict, /, **kwargs):
cls = type.__new__(mcls, name, bases, dict)
cls.descr = kwargs['descr']
def fn(self, args, *kwargs):
self.descr |= kwargs
cls.__call__ = fn
return cls
def __call__(cls, args, *kwargs):
obj = type.__call__(cls, *args)
obj.descr = kwargs
return obj

class Car(metaclass=AttrInitType, descr='Automobile'):
@property
def description(self):
return (type(self).descr+": "+" ".join(f"{k}={v}"
for k, v in self.descr.items()))

在自定义元类AttrInitType的__new__()方法中,直接进行了调用,仍采用解释器的语法糖cls = mcls(name, bases, dict)会导致无穷递归。类Car还可以写为不常用的等价形式:

def description(self):
return (type(self).descr+": "+" ".join(f"{k}={v}"
for k, v in self.descr.items()))
Car = AttrInitType('Car', (),
dict(description=property(description)), descr='Automobile')
del description

类的初始化也可以在其基类的中用初始化子类方法__init_subclass__()来设定:

| style="font-size: 200%; vertical-align: middle; padding: 1px 20px;" | →
|

|}

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

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

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

在语言和工具中的支持
下面是支持元类的一些最显著的编程语言。

  • Common Lisp,通过CLOS
  • Delphi和受它影响的其他Object Pascal版本
  • Groovy
  • Objective-C
  • Python
  • Perl,通过元类pragma,还有Moose
  • Ruby
  • Smalltalk
  • C++有关提议

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

是Prolog的面向对象扩展,它也支持元类。

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

另见

  • 元编程
  • 元对象
  • 反射式编程

引用

评论 (0)

  • 还没有评论,来抢沙发吧。