热门问题
时间线
聊天
视角

元類別

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

Remove ads

物件導向程式設計中,元類別(英語:metaclass)是一種實例是的類。普通的類別定義的是特定對象的行為,元類別定義的則是特定的類及其實例的行為。不是所有物件導向程式語言都支援元類別。在它能做的事情之中,元類別可以覆寫任何給定方面類行為的程度是不同的。元類別可以通過使類成為頭等對象來實現,在這種情況下元類別簡單的就是構造類的一個對象。每個語言都有它自己的元對象協定,給出對象、類和元類別如何互動的規則[1]

Smalltalk-80元類別

Smalltalk中,所有東西都是對象。此外,Smalltalk是類別為基的系統,這意味着所有對象都有一個類,它定義這個對象的結構(比如說這個類擁有實例變量),和這個對象所理解的訊息。二者在一起蘊含了,在Smalltalk中,類是一個對象,因此類也需要是它的元類別的實例。

元類別在Smalltalk-80系統中的主要角色,是提供協定來初始化類變量,和建立元類別的唯一實例(也就是其對應的類)的初始化實例。

實例聯絡

為了允許類擁有它們自己的方法,和叫作類別實例變量它們自己的實例變量,Smalltalk-80為每個類C介入了它們自己的元類別C class。就像實例方法實際上屬於類一樣,類別方法實際上屬於元類別。在類中定義實例變量類變量,而在元類別中定義類別實例變量。

每個元類別在效果上都是單例類。就像連體雙胞胎,類和元類別是共生的。元類別有一個實例變量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(對象類)。
  • 每個類,包括Object(對象類)、Class(類類)和Metaclass(元類別類),在尋找尋找方法時,首先尋找自己的元類別;然後按元類別繼承鏈上溯,最終經過Object class(對象元類別)而上溯至Class(類類);接着按類繼承鏈上溯ClassDescription(類描述類),最終經過Behavior(行為類)上溯至Object(對象類)。
  • 每個元類別,包括Metaclass class(元類別元類別),在尋找方法時,首先尋找Metaclass(元類別類);然後按類繼承鏈上溯ClassDescription(類描述類),最終經過Behavior(行為類)上溯至Object(對象類)。

類圖示意

下面是兩個示意圖,二者都是縱向連線表示實例聯絡,而橫向連線表示繼承聯絡。實例聯絡以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]。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()
Thumb

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

內省這個例子所建立的類與對象,和內建的所有類的基礎類別object與元類別type:

>>> inspect(b)
B None []
>>> inspect(B)
M ['A'] ['__doc__', '__firstlineno__', '__module__', '__static_attributes__', 'f']
>>> 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(type)
type ['object']
>>> review(object)
type []
>>> 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__', '__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']
>>> fullname_list(type.__subclasses__(type))
['abc.ABCMeta', 'enum.EnumType', 'ast._ABC', '__main__.M', 'typing._AnyMeta', 'typing.NamedTupleMeta', 'typing._TypedDictMeta']

在Python的數據模型中:

  • object字典減除type字典的差集中的方法特性含攝:
    • 標準類型層級英語Class hierarchy中的類別實例的特殊特性__class__[8]
    • 對象特殊方法中的基本客製化(customization)方法的字串表示、格式化字串表示、豐富比較和雜湊運算方法[9],和客製化化(customizing)類建立的類別方法__init_subclass__()的預設實現[10]
    • 標準庫的抽象基礎類別(ABC)模組中的ABCMeta元類別所建立諸類的方法__subclasshook__()的預設實現[11],和數據持久化模組中的做對象序列化的封存(pickling)類別實例方法的預設實現[12]
  • object字典type字典的交集中的方法特性含攝:
    • 對象特殊方法中基本客製化方法__init__()__new__()__repr__()[9],和客製化化特性訪問方法__getattribute__()__setattr__()__delattr__()__dir__()[13]
    • 標準類型層級英語Class hierarchy中的諸對象的特殊特性__doc__,以及由sys.getsizeof()呼叫的對象特殊方法__sizeof__()
  • type字典減除object字典的差集中的方法特性主要含攝:
    • 標準類型層級英語Class hierarchy中的自訂(custom)類的特殊特性__name____bases____mro____dict__和特殊方法__subclasses__()[14]
    • 對象特殊方法中的模擬可呼叫對象方法__call__()[15],和客製化化實例與子類檢查特殊方法__instancecheck__()__subclasscheck__()[16]
    • 對象特殊方法中的客製化化類建立的方法__prepare__()的預設實現[17],和數據描述器__abstractmethods__,這個特殊屬性由標準庫的抽象基礎類別(ABC)模組中的ABCMeta元類別在__new__()方法中為其實例進行設置[18],並可用函數update_abstractmethods()來更新[11]

然後使用元類別typetype.__instancecheck__(self, instance)type.__subclasscheck__(self, subclass),分別內省上述實例對象、類和元類別之間的實例關係和子類關係[16],以及通過方法描述器的__get__()方法將其轉變成內建方法:

>>> assert A.__subclasscheck__(B) is True
>>> assert object.__subclasscheck__(A) is True
>>> assert object.__subclasscheck__(B) is True
>>> assert object.__subclasscheck__(type) is True
>>> assert type.__subclasscheck__(type, M) is True
>>> inspect(A.__subclasscheck__)
builtin_function_or_method None None
>>> inspect(object.__subclasscheck__)
builtin_function_or_method None None
>>> inspect(type.__subclasscheck__)
method_descriptor None None
>>>
>>> differ_dir(type.__subclasscheck__, object.__subclasscheck__)
['__get__', '__objclass__']
>>> inspect(type.__subclasscheck__.__get__)
method-wrapper None None
>>> assert object.__subclasscheck__ == type.__subclasscheck__.__get__(object)
>>> differ_dir(object.__subclasscheck__, type.__subclasscheck__)
['__module__', '__self__']
>>> assert object.__subclasscheck__.__self__ is object
>>> assert object.__subclasscheck__.__module__ is None 
>>> 
>>> assert B.__instancecheck__(b) is True
>>> assert M.__instancecheck__ is type.__instancecheck__
>>> assert type.__instancecheck__(M, B) is True
>>> assert type.__instancecheck__(M, A) is True
>>> assert type.__instancecheck__(type, M) is True
>>> assert type.__instancecheck__(type, object) is True
>>> inspect(B.__instancecheck__)
builtin_function_or_method None None
>>> inspect(type.__instancecheck__)
method_descriptor None None
>>> assert B.__instancecheck__ == type.__instancecheck__.__get__(B)

接着內省__dict__特性和實例方法[19]

>>> import types
>>> assert b.__getattribute__ is B.__getattribute__ \
...     is object.__getattribute__ != type.__getattribute__
>>> assert b.__dict__ is object.__getattribute__(b, '__dict__')
>>> assert type(b.__dict__) is dict
>>> assert B.__dict__ == type.__getattribute__(B, '__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(A.__dict__['__dict__'].__get__) is types.MethodWrapperType
>>> assert b.__dict__ is A.__dict__['__dict__'].__get__(b)
>>> assert type(type.__dict__['__dict__']) is types.GetSetDescriptorType
>>> assert type(type.__dict__['__dict__'].__get__) is types.MethodWrapperType
>>> assert B.__dict__ == type.__dict__['__dict__'].__get__(B)
>>> 
>>> inspect(b.f)
method None []
>>> assert b.f is not b.f == b.f
>>> inspect(B.f)
function None []
>>> assert B.f is B.__dict__['f']
>>> 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

然後內省__dir__()方法和內建函數dir()

>>> assert b.__dir__ == b.__getattribute__('__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__') 
>>> 
>>> inspect(object.__dir__)
method_descriptor None None
>>> assert b.__dir__ == object.__dir__.__get__(b)
>>> assert {*object.__dir__.__get__(b)()} == {*object.__dir__(b)}
>>> assert {*object.__dir__(b)} == {*b.__dict__} | {*type.__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 {*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'
Remove ads

靜態方法與類別方法

Python中內建的靜態方法類和類別方法類,基於了非數據描述器協定[20]

>>> assert belong(staticmethod, __builtins__)
>>> assert belong(classmethod, __builtins__)
>>> differ(staticmethod, A)
['__call__', '__func__', '__get__', '__init__', '__isabstractmethod__', '__new__', '__repr__', '__wrapped__']
>>> differ(classmethod, A)
['__func__', '__get__', '__init__', '__isabstractmethod__', '__new__', '__repr__', '__wrapped__']
>>> inspect(staticmethod.__get__)
wrapper_descriptor None None
>>> inspect(classmethod.__get__)
wrapper_descriptor None None

下面是靜態方法描述器的純Python等價者,它模擬了cpython/Objects/funcobject.c中的PyStaticMethod_Type()[21]

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

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中,類型和方法可以在執行時動態建立[22],這裏的__get__(self, obj)通過方法類型MethodType,在從類或從實例發起呼叫而有傳入的類參照cls的常見情況下,建立將函數f繫結到了cls上的方法對象;在沒有傳入的類參照的特殊情況下,建立將函數f繫結到了type(obj)上的方法對象。

Remove ads

抽象基礎類別

下面承接上例,內省內建類型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.__subclasscheck__(numbers.Integral, int) is False
>>> assert type.__instancecheck__(numbers.Integral, 123) is False
>>> assert numbers.Integral.__subclasscheck__(int) is True
>>> assert numbers.Integral.__instancecheck__(123) is True
>>> 
>>> class MyInt(): pass
...
>>> numbers.Integral.register(MyInt)
<class '__main__.MyInt'>
>>> assert numbers.Integral.__subclasscheck__(MyInt) is True
>>> assert numbers.Integral.__instancecheck__(MyInt()) is True

接着內省抽象基礎類別ABC,和作為元類別type子類的抽象基礎類別元類別ABCMeta[23]

>>> from abc import ABC, ABCMeta
>>> review(ABC)
ABCMeta ['object']
>>> differ(ABC, A)
['__abstractmethods__', '__slots__', '_abc_impl']
>>> ABC.__abstractmethods__
frozenset()
>>> 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
>>> assert type.__instancecheck__(ABCMeta, numbers.Integral) is True
>>> assert numbers.Integral.__subclasscheck__ \
...     == ABCMeta.__subclasscheck__.__get__(numbers.Integral)
>>> assert numbers.Integral.__instancecheck__ \
...     == ABCMeta.__instancecheck__.__get__(numbers.Integral)

自訂元類別

下面的例子是一個最簡單的類Car,它在其__init__()方法中初始化了實例特性:

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'

這種任務也可以在自訂元類別的__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()))

元類別的type.__call__(self, /, *args, **kwargs)方法,將self作為函數來呼叫[15],這裏的形式參數args將會傳遞給實例對象的__init__()方法,直接採用obj = cls(*args)會導致無窮遞歸

這個例子的自訂元類別,在其__init__(self, *args)方法中,初始化了它所建立的實例類別的實例方法和類特性。其形式參數args所擷取的元組,包含了傳遞給元類別構造器type.__new__(name, bases, dict, /, **kwargs)的3個唯位置實際參數。name字串是新建類的名字,它會成為這個類的__name__特性。bases元組包含新建類的諸基礎類別,它會成為這個類的__bases__特性。dict字典包含新建類的主體中的特性和方法定義,它在成為這個類的__dict__特性之前可以被複製或包裝kwargs是關鍵字實際參數字典,它會被傳遞給適當的元類別機制即通常的__init_subclass__(),這種方式同於在類別定義中附加除metaclass之外的關鍵字參數[24]

這個自訂元類別也可以寫為:

class AttrInitType(type):
    def __new__(metaclass, name, bases, dict, /, descr='Product'):
        cls = type.__new__(metaclass, name, bases, dict)
        cls.descr = 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()))

在這裏的__new__()方法中,直接採用cls = metaclass(name, bases, dict)會導致無窮遞歸。類Car還可以寫為不常用的等價形式:

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

類的初始化也可以在其基礎類別的中用初始化子類別方法__init_subclass__()來設定[10]

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='Product', **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對映。變更可以圖示如下[25]

Smalltalk-80
隱式
元類別
終端
對象
Ruby
類的
本徵類
本徵類的
本徵類
終端
對象
終端對象的
本徵類

特別要注意在Smalltalk的隱含的元類別和Ruby類的本徵類之間的對應。Ruby的本徵類模型,使得隱式元類別概念完全統一:所有對象x,都有它自己的元對象,它叫作x的本徵類,它比x高一個元層級。高階本徵類通常是純粹概念上的存在,在大多數Ruby程式中,它們不包含任何方法也不儲存任何(其他)數據[26]

下面的示意圖展示Ruby樣例代碼的核心結構[27]。這裏的灰色節點表示打開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年代早期並具有學術價值[29]

Logtalk英語LogtalkProlog的物件導向擴充,它也支援元類別。

資源描述框架(RDF)和統一建模語言(UML)二者都支援元類別。

另見

參照

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads