メタクラスを用いたFlyweightパターン(typeクラスのサブクラス)
メタクラスを用いた方法の前に前提となるtype
クラスについて触れておく。type
クラスはクラス定義(=クラスオブジェクト)を生成する際に用いられる。
def methodA(self):
print('methodA')
# クラス名、親クラス(複数指定可)、名前空間(辞書により表現する、ここでは'methodA'にmethodA関数を紐付けている)を引数に取り
# クラスオブジェクトを生成する
ClassA = type('ClassA', (object,), {'methodA': methodA})
a = ClassA()
a.methodA()
# 以下の定義と同等
# class ClassA(object):
# def methodA(self):
# print('methodA')
前述の通り、type
はクラスである。そのためサブクラスを作成することができる。
サブクラスはtype
クラスの代わりにクラスオブジェクト生成に使用でき、__new__
メソッドや__init__
メソッドをオーバーライドすることで、クラスオブジェクト生成時に処理を追加できる。
def methodA(self):
print('methodA')
class Meta(type):
def __new__(cls, name, bases, namespace):
# クラスオブジェクト生成時に追加の処理を差し込める
print('hoge')
return super().__new__(cls, name, bases, namespace)
# typeクラスの代わりにMetaクラス(=typeクラスのサブクラス)を使用できる
ClassA = Meta('ClassA', (object,), {'methodA': methodA})
a = ClassA()
a.methodA()
通常のクラス定義構文でクラスオブジェクト生成時にtypeクラス以外を使用したい場合、Python3ではmetaclass
キーワードを使用する。この時に指定するクラスをメタクラスと呼ぶ。
class Meta(type):
def __new__(cls, name, bases, namespace):
# クラスオブジェクト生成時に追加の処理を差し込める
print('hoge')
return super().__new__(cls, name, bases, namespace)
class ClassA(object, metaclass=Meta):
# Python2のメタクラスの指定
#class ClassA(object):
# __metaclass__ =Meta
def methodA(self):
print('methodA')
a = ClassA()
a.methodA()
以下は、「クラスオブジェクト生成時に処理を追加できる」というメタクラスの性質を利用して、Flyweightパターンのロジックをクラスオジェクトの生成時に差し込む例である。
class MetaFlyweight(type):
def __new__(cls, name, bases, namespace):
# typeクラスの__new__メソッドはクラスオブジェクトを返す
clsobj = type.__new__(cls, name, bases, namespace)
clsobj._instances = {}
def _get_instance(cls, *args, **kargs):
instance = super(cls, cls).__new__(cls)
return cls._instances.setdefault(
(args, tuple(kargs.items())), instance)
# staticメソッドにしなくても動くが、以下の記述に従いstaticメソッドにしておく
# (ref.) https://docs.python.org/3.3/reference/datamodel.html#object.__new__
clsobj.__new__ = staticmethod(_get_instance)
return clsobj
class Hoge(object, metaclass=MetaFlyweight):
# Python2のメタクラスの指定
#class Hoge(object):
# __metaclass__ = MetaFlyweight
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class Piyo(object, metaclass=MetaFlyweight):
# Python2のメタクラスの指定
#class Piyo(object):
# __metaclass__ = MetaFlyweight
def __init__(self, args1, kwargs1):
self.args1 = args1
self.kwargs1 = kwargs1
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b') is Piyo('a', kwargs1='b')
assert Piyo('a', kwargs1='b') is not Piyo('a', kwargs1='c')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')
メタクラスを用いたFlyweightパターン(関数)
しかし必ずtype
クラスのサブクラスを生成する必要はない。
クラスオブジェクト生成時には、「クラス名(文字列)」「親クラス(タプル)」「名前空間(辞書)」を与えたtypeクラスが呼び出される。ただしここで必要なのはtype
クラス(もしくはサブクラス)というよりも、「上記3つの引数を受け取るcallableなオブジェクト」なのだ。そのため、以下のように関数をメタクラスに指定することも可能である。
def meta_flyweight(name, bases, namespace):
clsobj = type(name, bases, namespace)
clsobj._instances = {}
def _get_instance(cls, *args, **kargs):
instance = super(cls, cls).__new__(cls)
return cls._instances.setdefault(
(args, tuple(kargs.items())), instance)
# staticメソッドにしなくても動くが、以下の記述に従いstaticメソッドにしておく
# (ref.) https://docs.python.org/3.3/reference/datamodel.html#object.__new__
clsobj.__new__ = staticmethod(_get_instance)
return clsobj
class Hoge(object, metaclass=meta_flyweight):
# Python2のメタクラスの指定
#class Hoge(object):
# __metaclass__ = meta_flyweight
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class Piyo(object, metaclass=meta_flyweight):
# Python2のメタクラスの指定
#class Piyo(object):
# __metaclass__ = meta_flyweight
def __init__(self, args1, kwargs1):
self.args1 = args1
self.kwargs1 = kwargs1
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b') is Piyo('a', kwargs1='b')
assert Piyo('a', kwargs1='b') is not Piyo('a', kwargs1='c')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')
*元の記事では更にlamdaを用いた例を紹介しているが、これはさすがにトリッキーなので省く。