定義 meta class


類別是type的實例,如果想要改變類別的建立與初始化,可以繼承type來達到。例如:
>>> class SomeMeta(type):
...   def __new__(metaclz, definedclzname, supers, attrs):
...     print('SomeMeta __new__', metaclz, definedclzname, supers, attrs)
...     return type.__new__(metaclz, definedclzname, supers, attrs)
...   def __init__(definedclz, definedclzname, supers, attrs):
...     print('SomeMeta __init__', definedclz, definedclzname, supers, attrs)
...
>>> Some = SomeMeta('Some', (object,), {'doSome' : (lambda self, x: print(x))})
SomeMeta __new__ <class '__main__.SomeMeta'> Some (<class 'object'>,) {'doSome':
 <function <lambda> at 0x0192A198>}
SomeMeta __init__ <class '__main__.Some'> Some (<class 'object'>,) {'doSome': <f
unction <lambda> at 0x0192A198>}
>>> s = Some()
>>> s.doSome()
>>> s.doSome(10)
10
>>>


在 上面的例子中,你繼承type建立了SomeMeta並定義其__new__()與__init__()方法,__new__()方法所傳回的實例,就是 最後的類別物件,而__init__()則進行該類別的初始化動作。在上例中,直接使用SomeMeta來建構類別實例,實際上,你可以這麼作:
>>> class Some(metaclass=SomeMeta):
...     def doSome(self, x):
...         print(x)
...
SomeMeta __new__ <class '__main__.SomeMeta'> Some () {'doSome': <function doSome
 at 0x0192A348>, '__module__': '__main__'}
SomeMeta __init__ <class '__main__.Some'> Some () {'doSome': <function doSome at
 0x0192A348>, '__module__': '__main__'}
>>> s = Some()
>>> s.doSome(20)
20
>>>


metaclass是個協定,當指明metaclass的類別時,Python會在剖析完類別定義後,使用所指定的metaclass來進行類別的建構與初始化,其作用就像前一個例子。

如果你的類別要繼承自某個父類別,並想要指定metaclass,則可以如下:
class Other(Parent, metaclass=OtherMeta):
    pass

由於type本身也是一個類別,使用類別建立物件時:
x = X(arg)

實際上等於是:
x = X.__call__(arg)

__call__()方法預設會呼叫X的__new__()與__init__()方法,所以,若你想改變一個類別建立實例與初始化的流程,則可以在定義meta class時定義__call__()方法:
>>> class SomeMeta(type):
...     def __call__(definedclz, *args, **kwargs):
...         print('__new__')
...         instance = definedclz.__new__(definedclz, *args, **kwargs)
...         print('__init__')
...         definedclz.__init__(instance, *args, **kwargs)
...         return instance
...
>>> class Some(metaclass=SomeMeta):
...     def __new__(clz):
...         print('Some __new__')
...         return object.__new__(clz)
...     def __init__(self):
...         print('Some __init__')
...
>>> s = Some()
__new__
Some __new__
__init__
Some __init__
>>>


這就是一個類別被呼叫、建立實例與初始化的過程,由__call__()呼叫__new__()與__init__()

基本的meta class定義,會是type的子類別,藉由metaclass=MetaClass的協定,可以在類別定義被剖析完後,繞送至指定的meta class,你可以定義meta class的__new__()方法,決定類別如何建立,定義meta class的__init__(),決定類別如何初始,定義meta class的__call__()方法,決定若使用類別來建構物件時,該如何進行物件的建立與初始。

一個有趣的事實是,metaclass並不僅僅可指定類別,事實上,Python會呼叫所指定物件的__call__()方法,並傳入物件本身、類別名稱、父類別資訊與特性。如果你知道,一個函式定義若是如下:
>>> def somefunc(arg):
...     print(arg)
...
>>> somefunc(10)
10
>>> somefunc.__call__(10)
10
>>>


就可以知道,metaclass也可以指定函式。例如:
>>> def metafunc(definedclzname, supers, attrs):
...     print(definedclzname, supers, attrs)
...     return type(definedclzname, supers, attrs)
...
>>> class Some(metaclass=metafunc):
...     def doSome(self):
...         print('XD')
...
Some () {'doSome': <function doSome at 0x0192F228>, '__module__': '__main__'}
>>>


在上例中,函式的傳回值將作為類別,metafunc的作用相當於meta class的__new__()與__init__(),以此為出發點,metaclass可以指定的對象可以是類別、函式或任何的物件,只要它具有__call__()方法。謹記得,當你指定的對象是type類別(或子類別)時,其實該類別也是一個物件,也是type的實例,也有預設的__call__()方法。

PS. 或許Python的metaclass這個名稱應該叫metaobject比較正確!