メタクラスとクラスの継承

【 目次 】

サブクラスのメタクラス

サブクラスのメタクラスは?

メタクラス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が生成されているのが確認できる。

ページのトップへ戻る