元类

面向对象程序设计中,元类(英語: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类的实例。

继承联系

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

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

Object class superclass == Class.

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

继承层级

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

  • Object,对象类是所有类的基础类,它为所有对象提供公共的方法,即公共的缺省行为。至少包括了:测试对象的功能比如class方法,比较对象,对象复制,访问对象的各部份,打印和存储对象,错误处理。
    • Behavior,行为类定义了拥有实例的对象所需要的最小状态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:

例子

下列例子展示,从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.
Implicit metaclasses in Smalltalk-80 - A sample structure

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

方法查找次序

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

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

类图示意

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

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

Python元类

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

概述

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

 
r = object
c = type

class M(type): pass
class A(metaclass=M): pass
class B(A):
    def f(self): pass

b = B()

下面先建立辅助函数,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__

内省这个例子所建立的对象与类实例对象及其实例方法[8],和内建的所有类的基类object与元类type:

>>> inspect(b)
B None []
>>> inspect(B)
M ['A'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__', 'f']
>>> inspect(b.f)
method None []
>>> assert b.f is not b.f == b.f
>>> inspect(B.f)
function None []
>>> inspect(B.f.__get__)
method-wrapper None None
>>> assert b.f == B.f.__get__(b)
>>> differ_dir(b.f, B.f)
['__func__', '__self__']
>>> assert b.f.__func__ is B.f
>>> assert b.f.__self__ is b
>>> 
>>> inspect(A)
M ['object'] ['__dict__', '__doc__', '__firstlineno__', '__module__', '__static_attributes__', '__weakref__']
>>> differ(A, B)
['__dict__', '__weakref__']
>>> name_list(A.__subclasses__())
['B']
>>> inspect(M)
type ['type'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__']
>>> 
>>> review(__builtins__)
module None
>>> assert belong(object, __builtins__)
>>> assert belong(type, __builtins__)
>>> review(object)
type []
>>> review(type)
type ['object']
>>> fullname_list(type.__subclasses__(type))
['abc.ABCMeta', 'enum.EnumType', 'ast._ABC', '__main__.M', 'typing._AnyMeta', 'typing.NamedTupleMeta', 'typing._TypedDictMeta']

这里的b.f is not b.f表示了id(b.f) != id(b.f),内建函数id()返回一个对象的“标识”,它是保证对于这个对象在其生命时期内唯一并且恒定的一个整数,对于CPython就是这个对象在主存储器中的地址,具有不交叠的生命时期的两个对象可以有相同的id()值。Python 3.13版本为自定义增加了__firstlineno____static_attributes__特性[9]

数据模型

下面继续内省Python的数据模型

>>> differ(object, type)
['__class__', '__eq__', '__format__', '__ge__', '__getstate__', '__gt__', '__hash__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__str__', '__subclasshook__']
>>> intersect(object, type)
['__delattr__', '__dir__', '__doc__', '__getattribute__', '__init__', '__new__', '__repr__', '__setattr__', '__sizeof__']
>>> differ(type, object)
['__abstractmethods__', '__annotate__', '__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']
  • object字典减除type字典的差集中的方法特性含摄:
    • 标准类型层级中,类实例的特殊特性__class__[10]
    • 对象特殊方法中,基本定制(customization)方法:字符串表示方法__repr__(),格式化字符串表示方法__str__()__format__(),丰富比较方法__lt__()__le__()__eq__()__ne__()__gt__()__ge__(),散列运算方法__hash__()[11]
    • 对象特殊方法中,定制化(customizing)类创建的类方法__init_subclass__()的缺省实现[12]
    • 标准库中,抽象基类ABC)模块的ABCMeta元类所创建诸类的方法__subclasshook__()的缺省实现[13]
    • 标准库中,封存pickle)模块的进行对象序列化的类实例方法__getstate__()__reduce__()__reduce_ex__()的缺省实现[14]
  • object字典type字典的交集中的方法特性含摄:
    • 标准类型层级中,诸对象的特殊特性__doc__
    • 对象特殊方法中,基本定制(customization)方法__init__()__new__()__repr__()[11]
    • 对象特殊方法中,定制化(customizing)特性访问方法__getattribute__()__setattr__()__delattr__()__dir__()[15]
    • 标准库中,sys.getsizeof()调用的对象特殊方法__sizeof__()
  • type字典减除object字典的差集中的方法特性主要含摄:
    • 标准类型层级中,自定义(custom)类的特殊特性__name____bases____mro____module____qualname__,取置描述器getset_descriptor类型的__dict__属性,以及特殊方法__subclasses__()mro()[9]
    • 对象特殊方法中,模拟可调用对象方法__call__()的缺省实现[16]
    • 对象特殊方法中,定制化(customizing)实例与子类检查特殊方法__instancecheck__()__subclasscheck__()[17]
    • 对象特殊方法中,定制化(customizing)类创建的方法__prepare__()的缺省实现[18]
    • 标准库中,抽象基类ABC)模块设立的数据描述器__abstractmethods__[19],这个特殊属性ABCMeta元类在__new__()方法中为其实例进行设置[20]

type字典之中,Python 3.10版本介入了用于联合类型__or__(self, other)__ror__(self, other)方法[21],它们是模拟数值类型的特殊方法,分别实现了二元运算|和交换了运算元的二元运算|[22];Python 3.12版本中增加了__type_params__特性,Python 3.14版本更改了__annotations__特性并增加了__annotate__()方法[9];还有作为CPython实现细节的数据成员__basicsize____itemsize__[23]__flags__[24]__weakrefoffset__[25]__base__[26]__dictoffset__[27],以及给标准库inspect.signature()去做分析的__text_signature__特性。

实例关系与子类关系

使用特殊方法type.__instancecheck__(self, instance)type.__subclasscheck__(self, subclass),分别内省上述实例对象、类和元类之间的实例关系和子类关系[17]

>>> inspect(type.__subclasscheck__)
method_descriptor None None
>>> assert type.__subclasscheck__(type, M) is True
>>>
>>> inspect(type.__subclasscheck__.__call__)
method-wrapper None None
>>> inspect(type.__subclasscheck__.__get__)
method-wrapper None None
>>> assert M.__subclasscheck__.__objclass__ is type
>>> assert M.__subclasscheck__ is type.__subclasscheck__ \
...     != type.__subclasscheck__.__get__(type)
>>> assert object.__subclasscheck__ == type.__subclasscheck__.__get__(object)
>>> assert A.__subclasscheck__ == type.__subclasscheck__.__get__(A)
>>> inspect(object.__subclasscheck__)
builtin_function_or_method None None
>>> inspect(A.__subclasscheck__)
builtin_function_or_method None None
>>> assert object.__subclasscheck__(type) is True
>>> assert object.__subclasscheck__(M) is True
>>> assert object.__subclasscheck__(A) is True
>>> assert object.__subclasscheck__(B) is True
>>> assert A.__subclasscheck__(B) is True+
>>> 
>>> inspect(type.__instancecheck__)
method_descriptor None None
>>> assert type.__instancecheck__(type, object) is True
>>> assert type.__instancecheck__(type, M) is True
>>> assert type.__instancecheck__(M, B) is True
>>> assert type.__instancecheck__(M, A) is True
>>> 
>>> inspect(type.__instancecheck__.__call__)
method-wrapper None None
>>> inspect(type.__instancecheck__.__get__)
method-wrapper None None
>>> assert M.__instancecheck__.__objclass__ is type
>>> assert M.__instancecheck__ is type.__instancecheck__ \
...     != type.__instancecheck__.__get__(type)
>>> assert object.__instancecheck__ == type.__instancecheck__.__get__(object)
>>> assert B.__instancecheck__ == type.__instancecheck__.__get__(B)
>>> inspect(object.__instancecheck__)
builtin_function_or_method None None
>>> inspect(B.__instancecheck__)
builtin_function_or_method None None
>>> assert object.__instancecheck__(b) is True
>>> assert B.__instancecheck__(b) is True

方法描述器method_descriptor的实例,通过其方法包装器method-wrapper类型的__get__()方法,接受一个对象从而产生内建方法。描述器的实例可能出现__objclass__特性,它由标准库的inspect模块解释为指定这个对象在其中定义的那个类;对于具有__call__()方法的可调用对象,它可以指示这个给定类型或其子类的一个实例被预期或要求作为其第一个实际参数[28]。由于自定义元类可以覆写元类type__instancecheck__()__subclasscheck__()方法,这里尽量不采用元类的方法描述器转而采用它所产生的内建方法。

字典与目录

内省特殊特性__dict__,它的类型对于类实例对象就是字典dict,而对于对象是映射代理mappingproxy[29]

>>> 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__特性是实例方法的特殊只读特性[8]

取得特性与弱引用

内省体现了动态分派机制的特殊方法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个基类CDclass 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__[30]

静态方法与类方法

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

>>> 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()[32]

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()[33]

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

数据描述器

通过描述器协议可以建立数据描述器[31],描述器类定义并指称参与到其他类之中的,成为其所拥有的非固有性质

在一个类的定义中用描述器的构造器初始化一个特性名字,使其指定了这个描述器的一个实例,从而建立了这个类与这个描述器之间的联系(relationship)。数据描述器结合弱引用字典类,用于存储拥有这个描述器的一个的诸实例所关联数据的示例,可以参见备忘录模式的Python表述代码。

内建的属性property可以建立一个数据描述器,在对拥有一个属性描述器的类实例的一个特性进行访问或变更之时,它触发访问子或变异子函数调用。

>>> import types
>>> assert belong(property, __builtins__)
>>> inspect(property)
type ['object'] ['__delete__', '__doc__', '__get__', '__getattribute__', '__init__', '__isabstractmethod__', '__name__', '__new__', '__set__', '__set_name__', 'deleter', 'fdel', 'fget', 'fset', 'getter', 'setter']
>>> assert type(property.__init__) \
...     is type(property.__get__) \
...     is type(property.__set__) \
...     is type(property.__getattribute__) \
...     is types.WrapperDescriptorType
>>> assert type(property.__isabstractmethod__) \
...     is types.GetSetDescriptorType
>>> assert type(property.__set_name__) \
...     is type(property.getter) \
...     is type(property.setter) \
...     is type(property.deleter) \
...     is types.MethodDescriptorType
>>> assert type(property.fget) \
...     is type(property.fset) \
...     is type(property.fdel) \
...     is types.MemberDescriptorType

这里的__set_name__(self, owner, name)是定制化(customizing)类创建的特殊方法,它在创建拥有者类owner之时被自动调用,这个对象在这个类中已经被指派了名字name[12]

下面的例子把属性描述器用作修饰符,将对加以名字修饰的私有变量x访问,包装入一个属性描述器:

>>> class C():
...     def __init__(self, *args, **kwargs):
...         self.__x = None
...     @property
...     def x(self):
...         """I'm the 'x' property."""
...         return self.__x
...     @x.setter
...     def x(self, value):
...         self.__x = value
...     @x.deleter
...     def x(self):
...         del self.__x
... 
>>> inspect(C)
type ['object'] ['__dict__', '__doc__', '__firstlineno__', '__init__', '__module__', '__static_attributes__', '__weakref__', 'x']
>>> assert type(C.x) is property
>>> assert C.x.__name__ == 'x'
>>> assert C.x.setter == property.setter.__get__(C.x)
>>> assert type(property.__getattribute__(C.x, 'fget')) \
...     is type(property.fget.__get__(C.x)) \
...     is types.FunctionType
>>> c = C()
>>> assert '_C__x' in c.__dict__
>>> assert c.x is C.x.__get__(c)

调用描述器实例的非包装描述器类型的方法的示例,还可以参见作为Common Lisp对象系统核心特征的多分派机制的Python模拟表述代码,其中通过采用标准库functools中的singledispatchmethod,将方法变换为单一分派泛化函数[35]

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

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[13]。这里定义Property.__isabstractmethod__属性所用到的属性描述器property,模拟的是定义property.__isabstractmethod__属性所用到的取置描述器getset_descriptor。在前面章节中模拟的静态方法和类方法,也可以类似的用属性描述器property来定义StaticMethod.__isabstractmethod__ClassMethod.__isabstractmethod__属性。

抽象基类

内省作为元类type子类的抽象基类元类ABCMeta,和抽象基类ABC[19]

>>> 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()同其他方法描述器一起应用之时,它应当被用作最内的修饰符[13]。抽象基类在设计模式中主要用于实现接口

下面内省内建类型int抽象基类numbers.Integral:

>>> import numbers
>>> assert belong(int, __builtins__)
>>> review(int)
type ['object']
>>> review(numbers.Integral)
ABCMeta ['Rational']
>>> name_list(numbers.Integral.__mro__)
['Integral', 'Rational', 'Real', 'Complex', 'Number', 'object']
>>> differ(numbers.Integral, int)
['__abstractmethods__', '__firstlineno__', '__module__', '__slots__', '__static_attributes__', '_abc_impl']
>>> 
>>> assert type.__instancecheck__(ABCMeta, numbers.Integral) is True
>>> assert type.__subclasscheck__(numbers.Integral, int) is False
>>> assert type.__instancecheck__(numbers.Integral, 123) is False
>>> assert numbers.Integral.__subclasscheck__ \
...     == ABCMeta.__subclasscheck__.__get__(numbers.Integral)
>>> assert numbers.Integral.__instancecheck__ \
...     == ABCMeta.__instancecheck__.__get__(numbers.Integral)
>>> assert numbers.Integral.__subclasscheck__(int) is True
>>> assert numbers.Integral.__instancecheck__(123) is True
>>> 
>>> assert numbers.Integral.register \
...     == ABCMeta.register.__get__(numbers.Integral)
>>> class MyInt(): pass
...
>>> numbers.Integral.register(MyInt)
<class '__main__.MyInt'>
>>> assert numbers.Integral.__subclasscheck__(MyInt) is True
>>> assert numbers.Integral.__instancecheck__(MyInt()) is True

抽象基类元类ABCMeta覆写了元类type__instancecheck__()__subclasscheck__()方法,并且提供了同抽象基类建立子类关系和实例关系的注册方法register()

自定义元类

在一个类中定义__new__()方法,它所覆写是其基类的__new__()方法,可以定制这个类的实例的创建,比如单例模式享元模式的Python表述代码。

在一个类中定义__init__()方法,用来初始化其实例的特性,下面的例子是一个最简单的类Car

class Car():
    descr = 'Automobile'
    def __init__(self, *args, **kwargs):
        self.descr = kwargs
    def __call__(self, *args, **kwargs):
        self.descr |= kwargs
    @property
    def description(self):
        return (type(self).descr+": "+" ".join(f"{k}={v}"
            for k, v in self.descr.items()))
>>> car = Car(make='Toyota', model='Prius', year=2005, engine='Hybrid')
>>> car(color='Green')
>>> car.description
'Automobile: make=Toyota model=Prius year=2005 engine=Hybrid color=Green'

这种类实例初始化的任务,也可以在自定义(custom)元类的__call__()方法中完成:

class AttrInitType(type):
    def __init__(cls, *args):
        cls.descr = 'Automobile'
        def fn(self, *args, **kwargs):
            self.descr |= kwargs
        cls.__call__ = fn
    def __call__(cls, *args, **kwargs):
        obj = type.__call__(cls, *args) 
        obj.descr = kwargs
        return obj

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

在自定义元类AttrInitType__call__(cls, *args, **kwargs)方法中,直接进行了type.__call__(cls, *args)调用,仍采用解释器的语法糖obj = cls(*args)会导致无穷递归。元类的type.__call__(self, /, *args, **kwargs)方法,将self作为函数来调用[16]。这里的形式参数args,将会传递给所创建的实例对象的__init__()方法,而形式参数kwargs被截留于此,在所创建实例对象自身初始化之后对它进行相关的初始设置。

在自定义元类AttrInitType__init__(self, *args)方法中,初始化了它所创建的实例类的实例方法和类特性。其形式参数args所捕获的元组,包含了传递给作为内建函数的元类构造器type(name, bases, dict, /, **kwargs)的3个唯位置实际参数。元类构造器的实际参数为[37]

  • 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__()方法中,直接进行了type.__new__(mcls, name, bases, dict)调用,仍采用解释器的语法糖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__()来设定[12]

class AttrInitType(type):
    def __call__(cls, *args, **kwargs):
        obj = type.__call__(cls, *args) 
        obj.descr = kwargs
        return obj

class SubclassInit():
    def __init_subclass__(cls, /, descr, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.descr = descr
        def fn(self, *args, **kwargs):
            self.descr |= kwargs
        cls.__call__ = fn

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

Ruby元类

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

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

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

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

 
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增加“单例方法”的结果而被求值(被分配)。

在语言和工具中的支持

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

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

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

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

另见

引用

  1. ^ Ira R. Forman and Scott Danforth. Putting Metaclasses to Work. 1999. ISBN 0-201-43305-2. 
  2. ^ Learning Research Group. How To Use the Smalltalk-76 System (PDF) (报告). Xerox Palo Alto Research Center. October 1979 [2022-03-13]. (原始内容 (PDF)存档于2022-04-12). To define a new class, select a class category in the first pane of the browse window. This selection specifies the category to which the new class will be added, and causes a template to appear in the largest pane of the browse window, the code pane. ……
    The template presented in the code pane looks as follows
        Class new title: ’NameofClass’
        subclassof: Object
        fields: ’names of fields’
        declare: ’names of class variables’
     
  3. ^ Alan Kay. The Early History of Smalltalk. The most puzzling strange idea – at least to me as a new outsider – was the introduction of metaclasses (really just to make instance initialization a little easier – a very minor improvement over what Smalltalk-76 did quite reasonably already). Peter’s 1989 comment is typical and true: “metaclasses have proven confusing to many users, and perhaps in the balance more confusing than valuable.” …… My guess is that Smalltalk had moved into the final phase I mentioned at the beginning of this story, in which a way of doing things finally gets canonized into an inflexible belief structure. 
  4. ^ GNU Smalltalk Library Reference — Base classes — Metaclass. I am the root of the class hierarchy. My instances are metaclasses, one for each real class. My instances have a single instance, which they hold onto, which is the class that they are the metaclass of. I provide methods for creation of actual class objects from metaclass object, and the creation of metaclass objects, which are my instances. If this is confusing to you, it should be...the Smalltalk metaclass system is strange and complex. 
  5. ^ The core structure of Smalltalk-80. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始内容存档于2021-05-06). 
  6. ^ ProtoObject. [2022-01-16]. (原始内容存档于2022-03-12). 
  7. ^ PEP 3115 – Metaclasses in Python 3000. [2023-03-21]. (原始内容存档于2023-04-02). 
  8. ^ 8.0 8.1 The Python Language Reference — Data model — The standard type hierarchy — Callable types — Instance methods. 
  9. ^ 9.0 9.1 9.2 The Python Language Reference — Data model — The standard type hierarchy — Custom classes. 
  10. ^ The Python Language Reference — Data model — The standard type hierarchy — Class instances. 
  11. ^ 11.0 11.1 The Python Language Reference — Data model — Special method names — Basic customization. 
  12. ^ 12.0 12.1 12.2 The Python Language Reference — Data model — Special method names — Customizing class creation. 
  13. ^ 13.0 13.1 13.2 The Python Standard Library — abc — Abstract Base Classes. 
  14. ^ The Python Standard Library — pickle — Python object serialization — Pickling Class Instances. 
  15. ^ The Python Language Reference — Data model — Special method names — Customizing attribute access. 
  16. ^ 16.0 16.1 The Python Language Reference — Data model — Special method names — Emulating callable objects. 
    cpython/Objects/typeobject.c. __call__($self, /, *args, **kwargs)……Call self as a function. 
  17. ^ 17.0 17.1 The Python Language Reference — Data model — Special method names — Customizing instance and subclass checks. 
  18. ^ The Python Language Reference — Data model — Special method names — Customizing class creation — Preparing the class namespace. 
  19. ^ 19.0 19.1 PEP 3119 – Introducing Abstract Base Classes. 
  20. ^ cpython/Objects/typeobject.c. type itself has an __abstractmethods__ descriptor …… __abstractmethods__ should only be set once on a type, in abc.ABCMeta.__new__ …… 
  21. ^ The Python Language Reference — Built-in Types — Type Annotation Types — Union Type. 
  22. ^ The Python Language Reference — Data model — Special method names — Emulating numeric types. 
  23. ^ Python/C API Reference Manual — Type Object Structures — PyTypeObject Slots — tp_basicsize, tp_itemsize. 
  24. ^ Python/C API Reference Manual — Type Object Structures — PyTypeObject Slots — tp_flags. 
  25. ^ Python/C API Reference Manual — Type Object Structures — PyTypeObject Slots — tp_weaklistoffset. 
  26. ^ Python/C API Reference Manual — Type Object Structures — PyTypeObject Slots — tp_base. 
  27. ^ Python/C API Reference Manual — Type Object Structures — PyTypeObject Slots — tp_dictoffset. 
  28. ^ The Python Language Reference — Data model — Special method names — Customizing attribute access — Implementing Descriptors. 
  29. ^ The Python Language Reference — Data model — The standard type hierarchy — Instance methods. 
  30. ^ The Python Language Reference — Data model — Customizing attribute access. 
  31. ^ 31.0 31.1 Descriptor Guide — Descriptor protocol. 
  32. ^ Descriptor Guide — Pure Python Equivalents — Static methods. 
  33. ^ Descriptor Guide — Pure Python Equivalents — Class methods. 
  34. ^ The Python Standard Library — types — Dynamic type creation and names for built-in types. 
  35. ^ The Python Standard Library — Functional Programming Modules — functools. 
  36. ^ Descriptor Guide — Pure Python Equivalents — Properties. 
  37. ^ The Python Standard Library — Built-in Functions — type(). 
  38. ^ Introduction - Introductory sample. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始内容存档于2021-05-06). 
  39. ^ Paolo Perrotta. Metaprogramming Ruby 2 (PDF). Pragmatic Bookshelf. 2014 [2022-03-30]. ISBN 978-1-94122-212-6. (原始内容 (PDF)存档于2022-05-15). 
  40. ^ The core structure of Ruby - Eigenclass actuality. Object Membership - The Core Structure of Object Technology. [2021-03-29]. (原始内容存档于2021-05-06). 
  41. ^ Herb Sutter. Metaclasses (PDF). [2020-09-25]. (原始内容存档 (PDF)于2020-11-11). 
  42. ^ An implementation of mixins in Java using metaclasses (PDF). [2007-11-27]. (原始内容 (PDF)存档于2007-10-16).