热门问题
时间线
聊天
视角
元类
来自维基百科,自由的百科全书
Remove ads
在面向对象程序设计中,元类(英语:metaclass)是一种实例是类的类。普通的类定义的是特定对象的行为,元类定义的则是特定的类及其实例的行为。不是所有面向对象编程语言都支持元类。在它能做的事情之中,元类可以覆写任何给定方面类行为的程度是不同的。元类可以通过使类成为头等对象来实现,在这种情况下元类简单的就是构造类的一个对象。每个语言都有它自己的元对象协议,给出对象、类和元类如何交互的规则[1]。
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
消息[2][3]。在Smalltalk-80中,Class
是元类的基础类,它是类而不是元类。所有元类都是一个Metaclass
类的实例。Metaclass
类是Metaclass class
的实例,而Metaclass class
作为元类,也是Metaclass
类的实例。
Remove ads
在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
,元类类是创建元类的类,它为所有元类提供公共的方法[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派生的Squeak和Pharo的样例代码的结构[5],它们的继承层级的根类实际上是ProtoObject
,ProtoObject
封装了所有对象都必须拥有的极小化的消息集合,它被设计为引发尽可能多的错误,用来支持代理(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.
|
![]() |
这个结构由两个部分构成,用户部分有四个显式的对象和类及其两个隐式的元类:终端对象u
和v
,它们连接到的类A
和B
,它们两个连接到的右侧灰色节点表示的隐式的元类,其他的对象都是内置部分。
Remove ads
下面是方法查找次序的辨析:
- 每个终端对象,在查找方法时,都首先查找自己的类;然后按类继承链上溯,最终直接上溯至
Object
(对象类),而不经过Class
(类类)、Metaclass
(元类类)、ClassDescription
(类描述类)和Behavior
(行为类)。 - 每个类,包括
Object
、Class
和Metaclass
,在查找查找方法时,首先查找自己的元类;然后按元类继承链上溯,最终经过Object class
(对象元类)而上溯至Class
;接着按类继承链上溯ClassDescription
(类描述类),最终经过Behavior
(行为类)上溯至Object
(对象类)。 - 每个元类,包括
Metaclass class
,在查找方法时,因为都是Metaclass
的实例,所以首先查找Metaclass
;然后按类继承链上溯ClassDescription
(类描述类),最终经过Behavior
(行为类)上溯至Object
(对象类)。
Remove ads
下面是两个示意图,二者都是纵向连线表示实例联系,而横向连线表示继承联系。实例联系以Metaclass
(元类类)及其元类为顶端,而继承联系以Object
(对象类)及其元类为中心,其中Object class
(对象元类)继承Class
(类类)是串接元类继承链与类继承链的关键环节。前者图示将Metaclass
及其元类放置在最上方的独立两行,使得实例联系尽量成为树状向上汇聚;后者图示将Metaclass
及其元类放置在最左边,使得继承联系尽量都在同一行之上。
-
Smalltalk中在类和元类之间的继承和实例联系的示意图,这里从左至右,第一列是Metaclass元类和Metaclass(元类类),第二列是Class元类和Class(类类),第三列是ClassDescription元类与Behavior元类、和ClassDescription(类描述类)与Behavior(行为类),第四列是Object元类、Object(对象类)和Object实例,第五列是Foo元类、Foo类和Foo实例,第六列是Bar元类、Bar类和Bar实例。
Remove ads
Objective-C元类
在Objective-C中的元类,几乎同于Smalltalk-80的元类,这是因为Objective-C从Smalltalk引进了很多东西。就像Smalltalk,在Objective-C中实例变量和方法是对象的类定义的。类也是对象,因此它是元类的一个实例。
-
在Objective-C中在类和元类之间的继承和实例联系的示意图。注意Objective-C有多个根类,每个根类都有独立的层级。这个示意图只展示了例子根类NSObject的层级。每个其他根类都有类似的层级。
就像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()
|
![]() |
下面是对例子所建立的类和对象的内省:
>>> 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']
object
字典减除type
字典的差集中的方法或特性含摄:object
字典与type
字典的交集中的方法或特性含摄:type
字典减除object
字典的差集中的方法或特性主要含摄:- 标准类型层级中的做定制(custom)类的特殊特性
__name__
、__bases__
、__mro__
和__dict__
和特殊方法__subclasses__()
[15]; - 对象特殊方法中的模拟可调用对象方法
__call__()
[16],和定制化实例与子类检查特殊方法__instancecheck__()
和__subclasscheck__()
[17]。 - 对象特殊方法中的定制化类创建的方法
__prepare__()
的缺省实现[18],和数据描述器__abstractmethods__
,这个特殊属性由标准库的抽象基类(ABC)模块中的ABCMeta
元类在__new__()
方法中为其实例进行设置[19],并可用函数update_abstractmethods()
来更新[12]。
- 标准类型层级中的做定制(custom)类的特殊特性
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的隐含的元类和Ruby类的本征类之间的对应。Ruby的本征类模型,使得隐式元类概念完全统一:所有对象x
,都有它自己的元对象,它叫作x
的本征类,它比x
高一个元层级。高阶本征类通常是纯粹概念上的存在,在大多数Ruby程序中,它们不包含任何方法也不存储任何(其他)数据[23]。
下面的示意图展示Ruby样例代码的核心结构[24]。这里的灰色节点表示打开A
和v
本征类后扩张出来的本征类。
r = BasicObject
c = Class
class A; end
class B < A; end
u = B.new
v = B.new
class << A; end
class << v; end
|
![]() |
在语言和工具中的支持
下面是支持元类的一些最显著的编程语言。
- Common Lisp,通过CLOS
- Delphi和受它影响的其他Object Pascal版本
- Groovy
- Objective-C
- Python
- Perl,通过元类pragma,还有Moose
- Ruby
- Smalltalk
- C++有关提议[25]
一些不甚广泛传播的语言支持元类,包括OpenJava、OpenC++、OpenAda、CorbaScript、ObjVLisp、Object-Z、MODEL-K、XOTcl和MELDC。其中几种语言可追溯日期至1990年代早期并具有学术价值[26]。
Logtalk是Prolog的面向对象扩展,它也支持元类。
另见
引用
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads