LoginSignup
2
2

More than 5 years have passed since last update.

FlyweightパターンをPythonic(?)にする(3)

Last updated at Posted at 2015-02-27

前前回前回に引き続き、こちらの記事を解釈した際のメモ。

メタクラスを用いた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を用いた例を紹介しているが、これはさすがにトリッキーなので省く。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2