热门问题
时间线
聊天
视角
元類別
来自维基百科,自由的百科全书
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
(對象類)。 - 每個類,包括
Object
(對象類)、Class
(類類)和Metaclass
(元類類),在查找查找方法時,首先查找自己的元類;然後按元類繼承鏈上溯,最終經過Object class
(對象元類)而上溯至Class
(類類);接着按類繼承鏈上溯ClassDescription
(類描述類),最終經過Behavior
(行為類)上溯至Object
(對象類)。 - 每個元類,包括
Metaclass class
(元類元類),在查找方法時,首先查找Metaclass
(元類類);然後按類繼承鏈上溯ClassDescription
(類描述類),最終經過Behavior
(行為類)上溯至Object
(對象類)。
下面是兩個示意圖,二者都是縱向連線表示實例聯繫,而橫向連線表示繼承聯繫。實例聯繫以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]。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):
def f(): pass
b = B()
|
![]() |
下面先建立輔助函數:
def review(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__]))
再內省這個例子所建立的類與對象,和內建的所有類的基類object
與元類type
:
>>> print(type(b).__name__, ' '*5, [*b.__dict__])
B []
>>> inspect(B)
M ['A'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__', 'f']
>>> assert dir(b) == dir(B) == sorted(b.__dir__()) != sorted(B.__dir__(B))
>>> assert vars(B) == B.__dict__
>>> 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__']
>>> assert type.__name__ in __builtins__.__dict__
>>> review(type)
type ['object']
>>> assert object.__name__ in __builtins__.__dict__
>>> review(object)
type []
>>> 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']
object
字典減除type
字典的差集中的方法或特性含攝:object
字典與type
字典的交集中的方法或特性含攝:type
字典減除object
字典的差集中的方法或特性主要含攝:- 標準類型層級中的做定製(custom)類的特殊特性
__name__
、__bases__
、__mro__
和__dict__
和特殊方法__subclasses__()
[14]; - 對象特殊方法中的模擬可調用對象方法
__call__()
[15],和定製化實例與子類檢查特殊方法__instancecheck__()
和__subclasscheck__()
[16]。 - 對象特殊方法中的定製化類創建的方法
__prepare__()
的缺省實現[17],和數據描述器__abstractmethods__
,這個特殊屬性由標準庫的抽象基類(ABC)模塊中的ABCMeta
元類在__new__()
方法中為其實例進行設置[18],並可用函數update_abstractmethods()
來更新[11]。
- 標準類型層級中的做定製(custom)類的特殊特性
接着內省標準類型層級中的實例方法[19],和對象特殊方法中的實行(implementing)描述器方法[20]:
>>> type(b.f)
<class 'method'>
>>> type(B.f)
<class 'function'>
>>> assert B.f is B.__dict__['f']
>>> sorted({*b.f.__dir__()} - {*B.f.__dir__()})
['__func__', '__self__']
>>> assert b.f.__func__ is B.f
>>> assert b.f.__self__ is b
>>> assert A.__subclasscheck__(B) is True
>>> assert object.__subclasscheck__(type) is True
>>> assert type.__subclasscheck__(type, M) is True
>>> type(object.__subclasscheck__)
<class 'builtin_function_or_method'>
>>> sorted({*object.__subclasscheck__.__dir__()} - {*type.__subclasscheck__.__dir__()})
['__module__', '__self__']
>>> assert object.__subclasscheck__.__self__ is object
>>> assert object.__subclasscheck__.__module__ is None
>>> type(type.__subclasscheck__)
<class 'method_descriptor'>
>>> sorted({*type.__subclasscheck__.__dir__()} - {*object.__subclasscheck__.__dir__()})
['__get__', '__objclass__']
>>> type(type.__subclasscheck__.__get__(object))
<class 'builtin_function_or_method'>
>>> assert type.__subclasscheck__.__get__(object)(type) is True
>>> assert B.__instancecheck__(b) is True
>>> assert M.__instancecheck__(M, B) is True
>>> assert type.__instancecheck__(type, M) is True
>>> type(object.__instancecheck__)
<class 'builtin_function_or_method'>
>>> type(type.__instancecheck__)
<class 'method_descriptor'>
Remove ads
下面承接上例,內省內建類型int
、抽象基類numbers.Integral
和作為元類type
子類的抽象基類元類ABCMeta
[21]:
>>> assert int.__name__ in __builtins__.__dict__
>>> review(int)
type ['object']
>>> import numbers
>>> review(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
>>> review(ABC)
ABCMeta ['object']
>>> sorted({*ABC.__dict__} - {*A.__dict__})
['__abstractmethods__', '__slots__', '_abc_impl']
>>> ABC.__abstractmethods__
frozenset()
>>> assert type.__instancecheck__(ABCMeta, numbers.Integral) is True
>>> review(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']
Remove ads
Python中的靜態方法和類方法修飾器,基於了非數據描述器協議[22],下面是靜態方法描述器的純Python等價者[23]:
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中,類型和方法可以在運行時動態創建[24],下面是類方法描述器的純Python等價者[23]:
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)
上的方法對象。
考慮下面這個最簡單的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
映射。變更可以圖示如下[25]:
|
→ |
|
特別要注意在Smalltalk的隱含的元類和Ruby類的本徵類之間的對應。Ruby的本徵類模型,使得隱式元類概念完全統一:所有對象x
,都有它自己的元對象,它叫作x
的本徵類,它比x
高一個元層級。高階本徵類通常是純粹概念上的存在,在大多數Ruby程序中,它們不包含任何方法也不存儲任何(其他)數據[26]。
下面的示意圖展示Ruby樣例代碼的核心結構[27]。這裡的灰色節點表示打開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++有關提議[28]
一些不甚廣泛傳播的語言支持元類,包括OpenJava、OpenC++、OpenAda、CorbaScript、ObjVLisp、Object-Z、MODEL-K、XOTcl和MELDC。其中幾種語言可追溯日期至1990年代早期並具有學術價值[29]。
Logtalk是Prolog的面向對象擴展,它也支持元類。
另見
引用
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads