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


横の横の人が教えてくれた[記事](http://yloiseau.net/articles/DesignPatterns/flyweight/)を解釈したメモ。

## GoFに忠実なFlyweightパターン

1つの対象に対して1つのファクトリが定義される。
以下のコードは元の記事とほとんど同じだが、名前付き引数を与えるように変更を加えている。

```python:
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class HogeFactory(object):
    def  __init__(self):
        self._instances = {}

    def get_instance(self, args1, kwargs1):
        if ((args1), (kwargs1)) not in self._instances:
            self._instances[((args1), (kwargs1))] = Hoge(args1, kwargs1=kwargs1)
        return self._instances[((args1), (kwargs1))]


class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

class PiyoFactory(object):
    def __init__(self):
        self._instances = {}

    def get_instance(self, args1, kwargs1='test1', kwargs2='test2'):
        if ((args1), (kwargs1, kwargs2)) not in self._instances:
            self._instances[((args1), (kwargs1, kwargs2))] = Piyo(args1, kwargs1=kwargs1, kwargs2=kwargs2)
        return self._instances[((args1), (kwargs1, kwargs2))]

hogeFactory = HogeFactory()
piyoFactory = PiyoFactory()

assert hogeFactory.get_instance(1, kwargs1=2) is hogeFactory.get_instance(1, kwargs1=2)
assert hogeFactory.get_instance(1, kwargs1=2) is not hogeFactory.get_instance(1, kwargs1=3)
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is piyoFactory.get_instance('a', kwargs1='b', kwargs2='c')
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is not piyoFactory.get_instance('a', kwargs1='b', kwargs2='d')
```

## 可変長引数を使ったFactoryの一般化

なぜ1つの対象に対して1つのファクトリが必要だったかといえば、各対象のコンストラクタのシグネチャが異なる=`get_instance`メソッドのシグネチャが異なるためだった。
Pythonの可変長引数を使えばシグネチャの違いを吸収することができる。

```python:
class FlyweightFactory(object):
    def __init__(self, cls):
        self._instances = {}
        self._cls = cls

    def get_instance(self, *args, **kwargs):
        return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))

class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

hogeFactory = FlyweightFactory(Hoge)
piyoFactory = FlyweightFactory(Piyo)

assert hogeFactory.get_instance(1, kwargs1=2) is hogeFactory.get_instance(1, kwargs1=2)
assert hogeFactory.get_instance(1, kwargs1=2) is not hogeFactory.get_instance(1, kwargs1=3)
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is piyoFactory.get_instance('a', kwargs1='b', kwargs2='c')
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is not piyoFactory.get_instance('a', kwargs1='b', kwargs2='d')
```

## Factoryのデコレータ化(クラス)

前述の`FlyweightFactory`をデコレータ化すると更にシンプルに書ける。

```python:
class Flyweight(object):
    def __init__(self, cls):
        self._instances = {}
        self._cls = cls

    def __call__(self, *args, **kwargs):
        return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))

@Flyweight
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

@Flyweight
class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

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', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
```

デコレータはつけた対象を引数にとり呼び出される。この処理と等価なコードをデコレータを使用せずに書くと以下のようになる。
`get_instance`を呼び出すかわりに、Flyweightインスタンスを関数として読んでいる違いはあるが、一つ前のスニペットとそう違いがないことが分かる。

(ref. http://docs.python.jp/3.4/reference/datamodel.html#object.__call__)

```python:
class Flyweight(object):
    def __init__(self, cls):
        self._instances = {}
        self._cls = cls

    def __call__(self, *args, **kwargs):
        return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))

class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

# Hoge,Piyoを引数に取り, Flyweightインスタンスを生成する。
Hoge = Flyweight(Hoge)
Piyo = Flyweight(Piyo)

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', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
```

## Factoryのデコレータ化(クロージャ)

更に言うとcallableでありさえすれば良いのでクラスである必要もない、というのが以下のクロージャを利用した実装。

```python:
def flyweight(cls):
    instances = {}
    return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))

@flyweight
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

@flyweight
class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

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', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
```

デコレータを使わない等価なコードは以下のようになる。

```python:
def flyweight(cls):
    instances = {}
    return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))

class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

Hoge = flyweight(Hoge)
Piyo = flyweight(Piyo)

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', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
```

このやり方は非常にスマートであるが、「`Hoge`/`Piyo`クラスのサブクラスを作れない」という欠点がある。
これはデコレータを使わないコードを見ると分かるが、`Hoge`/`Piyo`がもはやクラスではなく関数オブジェクトになっているためだ。従って以下のコードはエラーになる。

```python:
def flyweight(cls):
    instances = {}
    return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))

@flyweight
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

@flyweight
class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

# Hogeはもはやクラスではないため継承不可
class Hoge2(Hoge):
    def __init__(self, args1, kwargs1='test1'):
        super().__init__(args1, kwargs1=kwargs1)
```

デコレータにより`Hoge`/`Piyo`がクラスではなくなる、という意味ではデコレータをクラスで表現した場合も変わらないが、あちらは`Hoge`/`Piyo`クラスを置き変えた`Flyweight`クラスのインスタンスが元のクラスを保持しているのがミソ。
そのため、以下のようなコードでサブクラス化できる。

```python:
class Flyweight(object):
    def __init__(self, cls):
        self._instances = {}
        self._cls = cls

    def __call__(self, *args, **kwargs):
        return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))

@Flyweight
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

@Flyweight
class Piyo(object):
    def __init__(self, args1, kwargs1, kwargs2):
        self.args1 = args1
        self.kwargs1 = kwargs1
        self.kwargs2 = kwargs2

# Hoge(実体はFlyweightインスタンス)は元のクラスを保持するインスタンス変数_clsを持つ
class Hoge2(Hoge._cls):
    def __init__(self, args1, kwargs1='test1'):
        super().__init__(args1, kwargs1=kwargs1)

```