Flyweightパターン

概要

このパターンの目的は「インスタンスの再利用により、メモリ使用量を最小限に抑える」ことです。

例題

実践Python3では以下を紹介しています。

  • 変数を使用する方法
  • __slots__を使用する方法
  • DBMを使用する方法

それぞれ、以下のサンプルコードで見ていきます。

サンプルコード

# 変数を使用する方法

配列やタプル内で同じ数字や文字列を使用するときは、変数に入れ、それを参照するとメモリを節約することができます。

import sys


def main():
    r, g, b = "red", "green", "blue"

    tuple1 = ("red", "green", "blue", "red", "green", "blue", "red", "green")
    tuple2 = (r, g, b, r, g, b, r, g)

    # calc memory size(tuple1)
    memorysize_tuple1 = 0
    memorysize_tuple1 += sys.getsizeof(tuple1)
    for item in tuple1:
        memorysize_tuple1 += sys.getsizeof(item)

    # calc memory size(tuple2)
    memorysize_tuple2 = 0
    memorysize_tuple2 += sys.getsizeof(tuple2)
    for item in [r, g, b]:
        memorysize_tuple2 += sys.getsizeof(item)

    print(memorysize_tuple1, memorysize_tuple2)


if __name__ == "__main__":
    main()

・出力

536 271

tuple2の方がメモリ使用量を抑えています。


# __slots__ を使用する方法

__slots__を使用すると、新たにattributeを設定できなくなる代わりに、
メモリの使用量を節約することができます。

以下の例では __slots__を設定しないHoge1クラスと、__slots__を設定したHoge2クラスでの
挙動の違い、メモリの使用量を見ています。

import sys


def main():
    h1 = Hoge1()
    h2 = Hoge2()

    h1.add_att7()
    print(h1.att7)

    # h2.add_att7()  # => AttributeError
    # print(h2.att7)

    # calc memory size
    print(h1.__dict__)
    memorysize_h1 = sys.getsizeof(h1) + sys.getsizeof(h1.__dict__)

    # print(h2.__dict__)  # => AttributeError
    memorysize_h2 = sys.getsizeof(h2)

    print("h1: {}, h2: {}".format(memorysize_h1, memorysize_h2))


class Hoge1:

    # __slots__ = ("att1", "att2", "att3", "att4", "att5", "att6")

    def __init__(self, att1=0, att2=0, att3=0, att4=0, att5=0, att6=0):
        self.att1 = att1
        self.att2 = att2
        self.att3 = att3
        self.att4 = att4
        self.att5 = att5
        self.att6 = att6

    def add_att7(self):
        self.att7 = 7


class Hoge2:

    __slots__ = ("att1", "att2", "att3", "att4", "att5", "att6")

    def __init__(self, att1=0, att2=0, att3=0, att4=0, att5=0, att6=0):
        self.att1 = att1
        self.att2 = att2
        self.att3 = att3
        self.att4 = att4
        self.att5 = att5
        self.att6 = att6

    def add_att7(self):
        self.att7 = 7


if __name__ == "__main__":
    main()

・出力

7 {'att1': 0, 'att2': 0, 'att3': 0, 'att4': 0, 'att5': 0, 'att6': 0, 'att7': 7} h1: 208, h2: 88

h2の方がdictが無い分、メモリが節約できていることがわかります。


# DBM(shelve)を使用する方法

DBM(shelve)は書き込み先が外部のファイルになるようなdictです。

本質的には、以下のコードだけで使用することができます。

import shelve

# 書き込み
dbm = shelve.open("shelve") # => shelve.db が作られる
dbm["hoge"] = "hoge"
print("hoge")
dbm.close()

# 読み込み(一応別変数で読みこむ)
dbm2 = shelve.open("shelve")
print(dbm2["hoge"]) # => hoge
dbm2.close()
import sys
import shelve
import atexit


def main():
    h1 = Hoge1()
    h3 = Hoge3()

    memorysize_h1 = sys.getsizeof(h1) + sys.getsizeof(h1.att1) + sys.getsizeof(h1.att2)
    memorysize_h3 = sys.getsizeof(h3)

    print(memorysize_h1, memorysize_h3)


class Hoge1:

    def __init__(self, att1=0, att2=0):
        self.att1 = att1
        self.att2 = att2


class Hoge3:

    __dbm = shelve.open("point.db")
    atexit.register(__dbm.close)

    def __init__(self, att1=0, att2=0):
        self.att1 = att1
        self.att2 = att2

    def __key(self, name):
        return "{:X}:{}".format(id(self), name)

    def __getattr__(self, name):
        return Hoge3.__dbm[self.__key(name)]

    def __setattr__(self, name, value):
        Hoge3.__dbm[self.__key(name)] = value


if __name__ == "__main__":
    main()

・出力

104 56

これもdbmを使用した分メモリが節約できていることがわかるかと思います。

クラス図

# 一般的なクラス図

一般的なクラス図(Flyweightパターン)

# サンプルコードにおけるクラス図

省略

参考URL

Wikipedia - Flyweightパターン