Python 中「一切皆物件」,類別本身也是物件,其型別為 type。理解類別建構機制,需掌握 type 作為 metaclass 的角色,以及 __metaclass__、類別裝飾器的運作時機差異。
>>> class C(object):
... def __init__(self, s): print(s)
...
>>> myclass = C
>>> type(C)
<type 'type'>
>>> myclass(2) # 可呼叫建立實例
2
類別可指派給變數、傳遞給函式、動態建立:
>>> def make_class(name):
... class C(object):
... def print_name(self): print(name)
... C.__name__ = name
... return C
...
>>> C1, C2 = map(make_class, ['C1', 'C2'])
>>> C1().print_name() # C1
type(name, bases, attrs) 三參數形式即為動態建立類別:
>>> MyClass = type('MyClass', (object,), {'attr': 0})
>>> type(MyClass)
<type 'type'>
自訂 metaclass 需為可呼叫物件(函式或類別),接受 (name, bases, attrs) 並回傳類別:
>>> def my_meta(name, bases, attrs):
... print('Creating', name)
... return type(name, bases, attrs)
...
>>> class C(object):
... __metaclass__ = my_meta
...
Creating C
Metaclass 可在類別建立「前」攔截屬性,為所有可呼叫屬性包裝記錄器:
def log_everything_metaclass(name, bases, attrs):
new_attrs = {}
for k, v in attrs.items():
if hasattr(v, '__call__'):
new_attrs[k] = logged('%b %d %Y - %H:%M:%S', name + '.')(v)
else:
new_attrs[k] = v
return type(name, bases, new_attrs)
__doc__ 等唯讀屬性:Metaclass 可在 attrs 階段修改;裝飾器因類別已建立無法修改。type 建立 Metaclass 類別class Meta(type):
def __new__(cls, name, bases, attrs):
print('Meta.__new__')
return super(Meta, cls).__new__(cls, name, bases, attrs)
def __call__(self, *args, **kw):
print('Meta.__call__')
return super(Meta, self).__call__(*args, **kw)
類別宣告 → Metaclass __new__ → __init__ → 裝飾器 → 類別就緒 → 實例化時 Metaclass __call__ → __new__ → __init__。
Metaclass 可自動註冊子類別至字典,依幀識別碼快速查找:
frame_map = {}
class FrameFactory(type):
def __new__(cls, name, bases, attrs):
new_cls = super(FrameFactory, cls).__new__(cls, name, bases, attrs)
fid = attrs.get('frame_identifier')
if fid: frame_map[fid] = new_cls
return new_cls
class Frame(object):
__metaclass__ = FrameFactory
frame_identifier = None
class TitleFrame(Frame):
frame_identifier = 'TIT2'
同樣功能用裝飾器實作更簡潔,除非需在類別建立前修改屬性,否則建議優先使用裝飾器。
Metaclass 是強大但晦澀的技術,多數情況下裝飾器即可替代。深入理解有助於除錯與框架設計,實務上「三思而後行」。