メタクラスとクラスの継承
【 目次 】
サブクラスのメタクラス
サブクラスのメタクラスは?
メタクラスMyMetaをメタクラスとするクラスMyClsを継承したクラスのメタクラスは__metaclass__属性を指定しない場合、typeになるのであろうかそれともMyMetaになるのであろうか?
class MyMeta(type): pass class MyCls(object): __metaclass__=MyMeta class SubMyCls(MyCls): pass print type(SubMyCls)
実行結果
<class '__main__.MyMeta'>
__metaclass__属性を指定しない場合、派生クラスのメタクラスはスーパクラスのメタクラスと同じになるようである。
サブクラスのメタクラスに別のメタクラスを指定した場合は?
サブクラスのメタクラスにスーパクラスとは別のメタクラスを指定した場合はどうなるであろうか?
メタクラスMetaClsAを定義して、MetaClsAをメタクラスとするMyClsを定義。
class MetaClsA(type): pass class MyCls(object): __metaclass__=MetaClsA
MyClsをスーパクラスとするSubMyClsを定義してそのメタクラスに別のメタクラスMetaClsBを指定。
class MetaClsB(type): pass class SubMyCls(MyCls): __metaclass__=MetaClsB
実行結果
TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
メタクラスの衝突によりエラーが発生してしまう。
しかし、メタクラスMetaClsBがMetaClsAのサブクラスの場合にはメタクラスの衝突がおこらずエラーは発生しない。
class MetaClsB(MetaClsA): pass
メタクラスの多重継承
別のメタクラスをもつクラスどうしを多重継承すると、メタクラスの衝突が起りエラーが発生する。
MetaClsAをメタクラスにもつMyClsAを定義
class MetaClsA(type): pass class MyClsA(object): __metaclass__=MetaClsA
MetaClsBをメタクラスにもつMyClsBを定義
class MetaClsB(type): pass class MyClsB(object): __metaclass__=MetaClsB
MyClsAとMetaClsBを多重継承するMuliClsを定義する。
class MuliCls(MyClsA, MyClsB): pass
実行結果
TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
この場合、前述のようにMetaClsAとMetaClsBに継承関係を持たせることで解決する事もできるが、
class MetaClsB(MetaClsA): pass
MetaClsAとMetaClsBを独立して定義したい(継承関係を持たせたくない)場合には、MetaClsAとMetaClsBを多重継承するもう1つのメタクラスを定義する事により解決できる。
class MuliMetaCls(MetaClsA,MetaClsB): pass class MuliCls(MyClsA, MyClsB): __metaclass__=MuliMetaCls
しかし、MetaClsAとMetaClsBを多重継承するもう1つのメタクラスを定義するのが面倒だという向きは、次の記事によると、メタクラスの多重継承クラスを自動生成するモジュールが紹介されている。
そのソースコードを以下に引用。
noconflict.py
#coding: UTF-8 import inspect, types, __builtin__ ############## preliminary: two utility functions ##################### def skip_redundant(iterable, skipset=None): "Redundant items are repeated items or items in the original skipset." if skipset is None: skipset = set() for item in iterable: if item not in skipset: skipset.add(item) yield item def remove_redundant(metaclasses): skipset = set([types.ClassType]) for meta in metaclasses: # determines the metaclasses to be skipped skipset.update(inspect.getmro(meta)[1:]) return tuple(skip_redundant(metaclasses, skipset)) ################################################################## ## now the core of the module: two mutually recursive functions ## ################################################################## memoized_metaclasses_map = {} def get_noconflict_metaclass(bases, left_metas, right_metas): """Not intended to be used outside of this module, unless you know what you are doing.""" # make tuple of needed metaclasses in specified priority order metas = left_metas + tuple(map(type, bases)) + right_metas needed_metas = remove_redundant(metas) # return existing confict-solving meta, if any if needed_metas in memoized_metaclasses_map: return memoized_metaclasses_map[needed_metas] # nope: compute, memoize and return needed conflict-solving meta elif not needed_metas: # wee, a trivial case, happy us meta = type elif len(needed_metas) == 1: # another trivial case meta = needed_metas[0] # check for recursion, can happen i.e. for Zope ExtensionClasses elif needed_metas == bases: raise TypeError("Incompatible root metatypes", needed_metas) else: # gotta work ... metaname = '_' + ''.join([m.__name__ for m in needed_metas]) meta = classmaker()(metaname, needed_metas, {}) memoized_metaclasses_map[needed_metas] = meta return meta def classmaker(left_metas=(), right_metas=()): def make_class(name, bases, adict): metaclass = get_noconflict_metaclass(bases, left_metas, right_metas) return metaclass(name, bases, adict) return make_class
このモジュールを使うと
from noconflict import classmaker class MuliCls(MyClsA, MyClsB): __metaclass__=classmaker()
とする事で、MetaClsAとMetaClsBを多重継承したメタクラスを自動生成してくれる。
print type(MuliCls)
実行結果
<class 'noconflict._MetaClsAMetaClsB'>
MuliClsのメタクラスnoconflict._MetaClsAMetaClsBが生成されているのが確認できる。