話をおさらいすると、継承が可能なFlyweightパターンを
class Flyweight: def __new__(cls, name): print("Flyweight.__new__({}, {})".format(cls, repr(name))) if not cls.__dict__.get('_InstancePool'): cls._InstancePool = {} obj = cls._InstancePool.get(name, None) if not obj: obj = object.__new__(cls) cls._InstancePool[name] = obj print(" Flyweight.return {}".format(obj)) return obj def __init__(self, name): print("Flyweight.__init__({}, {})".format(self, repr(name))) self.name = name
のように書いたのですが、4〜5行目の、
if not cls.__dict__.get('_InstancePool'): cls._InstancePool = {}
の部分で、クラス変数の有無をインスタンスを生成する度に毎回チェックしているので、これをメタクラスで何とかしてみようという話です。
メタクラスはインスタンスがクラスオブジェクトになるクラスですので、普通のクラスで可能な、
- __new__() メソッドによる、インスタンスそのものを生成する処理のカスタマイズ
- __init__() メソッドによる、生成したインスタンスの初期設定
- __call__() メソッドによる、関数のように呼び出されたインスタンスの引数の動的な差し替え
は、メタクラスの場合はインスタンスであるクラスオブジェクトに対してそのまま適用できます。
今回はクラスオブジェクトの生成後に _InstancePool 属性を追加するだけですので、 __init__() メソッドを使いました。
class FlyweightMeta(type): def __init__(cls, *args): print("FlyweightMeta.__init__({}, {})".format(cls, *args)) super().__init__(*args) cls._InstancePool = {} class Flyweight(metaclass=FlyweightMeta): def __new__(cls, name): print("Flyweight.__new__({}, {})".format(cls, repr(name))) obj = cls._InstancePool.get(name, None) if not obj: obj = object.__new__(cls) cls._InstancePool[name] = obj print(" Flyweight.return {}".format(obj)) return obj def __init__(self, name): print("Flyweight.__init__({}, {})".format(self, repr(name))) self.name = name class ClassA(Flyweight): def __init__(self, name): print("ClassA.__init__({}, {})".format(self, repr(name))) self.name = name class ClassB(Flyweight): def __init__(self, name): print("ClassB.__init__({}, {})".format(self, repr(name))) self.name = name
いつものようにテストしてみます。
>>> from flyweight import ClassA, ClassB, Flyweight FlyweightMeta.__init__(<class 'flyweight.Flyweight'>, Flyweight) FlyweightMeta.__init__(<class 'flyweight.ClassA'>, ClassA) FlyweightMeta.__init__(<class 'flyweight.ClassB'>, ClassB) >>> A_a = ClassA('a') Flyweight.__new__(<class 'flyweight.ClassA'>, 'a') Flyweight.return <flyweight.ClassA object at 0x100550ad0> ClassA.__init__(<flyweight.ClassA object at 0x100550ad0>, 'a') >>> A_b = ClassA('b') Flyweight.__new__(<class 'flyweight.ClassA'>, 'b') Flyweight.return <flyweight.ClassA object at 0x100550b10> ClassA.__init__(<flyweight.ClassA object at 0x100550b10>, 'b') >>> A_a_clone = ClassA('a') Flyweight.__new__(<class 'flyweight.ClassA'>, 'a') Flyweight.return <flyweight.ClassA object at 0x100550ad0> ClassA.__init__(<flyweight.ClassA object at 0x100550ad0>, 'a') >>> B_a = ClassB('a') Flyweight.__new__(<class 'flyweight.ClassB'>, 'a') Flyweight.return <flyweight.ClassB object at 0x100550b50> ClassB.__init__(<flyweight.ClassB object at 0x100550b50>, 'a') >>> B_c = ClassB('c') Flyweight.__new__(<class 'flyweight.ClassB'>, 'c') Flyweight.return <flyweight.ClassB object at 0x100550b90> ClassB.__init__(<flyweight.ClassB object at 0x100550b90>, 'c') >>> A_c = ClassA('c') Flyweight.__new__(<class 'flyweight.ClassA'>, 'c') Flyweight.return <flyweight.ClassA object at 0x100550bd0> ClassA.__init__(<flyweight.ClassA object at 0x100550bd0>, 'c') >>> Flyweight_c = Flyweight('c') Flyweight.__new__(<class 'flyweight.Flyweight'>, 'c') Flyweight.return <flyweight.Flyweight object at 0x100550c50> Flyweight.__init__(<flyweight.Flyweight object at 0x100550c50>, 'c') >>> ClassA('a') is ClassB('a') Flyweight.__new__(<class 'flyweight.ClassA'>, 'a') Flyweight.return <flyweight.ClassA object at 0x100550ad0> ClassA.__init__(<flyweight.ClassA object at 0x100550ad0>, 'a') Flyweight.__new__(<class 'flyweight.ClassB'>, 'a') Flyweight.return <flyweight.ClassB object at 0x100550b50> ClassB.__init__(<flyweight.ClassB object at 0x100550b50>, 'a') False >>> ClassA('c') is ClassB('c') Flyweight.__new__(<class 'flyweight.ClassA'>, 'c') Flyweight.return <flyweight.ClassA object at 0x100550bd0> ClassA.__init__(<flyweight.ClassA object at 0x100550bd0>, 'c') Flyweight.__new__(<class 'flyweight.ClassB'>, 'c') Flyweight.return <flyweight.ClassB object at 0x100550b90> ClassB.__init__(<flyweight.ClassB object at 0x100550b90>, 'c') False
問題ないようなので、整理してみました。
#!/usr/bin/env python3.1 class FlyweightMeta(type): """This metaclass is used to initialize subclasses of Flyweight class.""" def __init__(cls, *args): super().__init__(*args) cls._InstancePool = {} class Flyweight(metaclass=FlyweightMeta): """Implements the Flyweight design pattern. >>> class ClassA(Flyweight): ... pass ... >>> A_a = ClassA('a') >>> A_b = ClassA('b') >>> A_a is A_b False >>> A_a_cloned = ClassA('a') >>> A_a is A_a_cloned True >>> class ClassB(Flyweight): ... pass ... >>> B_a = ClassB('a') >>> A_a is B_a False Original code by Duncan Booth <http://www.suttoncourtenay.org.uk/duncan/accu/pythonpatterns.html#id17> """ def __new__(cls, name): obj = cls._InstancePool.get(name, None) if not obj: obj = object.__new__(cls) cls._InstancePool[name] = obj return obj if __name__ == "__main__": import doctest doctest.testmod()
とりあえず動きましたので、これでよしとします。
本当は、せっかくメタクラスを使ったので、Flyweight.__new__ の中身も FlyweightMeta.__call__ に追い出したかったのですが、上手くいかないパターンが見つかったので没にしました……orz
0 件のコメント:
コメントを投稿