Singletonパターン
Singletonはデザインパターンの1つで、クラスのインスタンスオブジェクトをただ1つにすることができます。これを利用することで、例えばデータベースにコネクションするためのクラスを1つにすることで、複数のインスタンスが同時にデータベースを操作しないという前提でコードを書けるようになるため同期やメモリ内のデータを管理することができるようになります。
また、並行性の制御も簡単になります。例えば一意なドキュメントIDを予約するドキュメントがある場合、その作業をするユーティリティは1つである方が良いので、Singletonパターンを採用すると良いです。
__new__()
メソッドをオーバーライトする方法
__new__()
メソッドをオーバーライトして以下のように実装できます。
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
このクラスから複数のインスタンスを生成し、IDを確認してみます。同じオブジェクトが作られていることが確認できると思います。
obj1 = Singleton()
obj2 = Singleton()
print(id(obj1) == id(obj2))
# True
print(obj1 == obj2)
# True
しかし、この方法は落とし穴があります。このクラスを継承したサブクラスを作り、それぞれインスタンスを生成してみます。
class SubSingleton(Singleton):
pass
print(Singleton())
# <__main__.Singleton object at 0x10cfda9e8>
print(SubSingleton())
# <__main__.Singleton object at 0x10cfda9e8>
この結果を見ますと、サブクラスのインスタンスはベースクラスのインスタンスとして生成されてしまいます。それだけではなくインスタンスの生成順序によって挙動が変わってしまう問題もあります。
class SubSingleton(Singleton):
pass
print(SubSingleton())
# <__main__.SubSingleton object at 0x10c3f57b8>
print(Singleton())
# <__main__.Singleton object at 0x10c3f5748>
順序によって振る舞いが異なると動作を予測するのが難しくなってしまいます。特に大規模なアプリケーションになるとデバッグが困難になってしまいます。ユーザの動作やimportの順番によってアプリケーションが破壊されてしまうかもしれないからです。
通常、Singletonクラスは継承して使うことは避けるべきです。__new__()
メソッドをオーバーライトする方法もサブクラスを作るようなことをしなければ安全な実装ではあります。言語によっては、例えばC#ではsealed修飾子を使うことで継承を禁止することができます。しかし、Pythonは継承を禁止することができないので誤って実装する恐れがあります。そのため継承される場合も考えた方がより安全です。
メタクラスを使用する方法
メタクラスの__call__()
メソッドをオーバーライトすることでクラスをSingletonにすることができます。
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Singleton(metaclass = MetaSingleton):
pass
class SubSingleton(Singleton):
pass
print(Singleton() == Singleton())
# True
print(SubSingleton() == SubSingleton())
# True
print(Singleton())
# <__main__.Singleton object at 0x10ce84ef0>
print(SubSingleton())
# <__main__.SubSingleton object at 0x10ce84e48>
# 生成順序を逆にしてみる
print(SubSingleton())
# <__main__.SubSingleton object at 0x10ce84c18>
print(Singleton())
# <__main__.Singleton object at 0x10ce84cf8>
上の実装を確認しますと、各クラスから生成されるインスタンスは1つだけで、生成順序にも依存しない実装をすることができますので、__new__()
メソッドをオーバーライトする方法よりも安全だと思います。
Borgで置き換える実装
この方法は「Python クックブック」の著者であるAlex Martelliという方が提案した方法です。Singletonのように振る舞いをしますが、構造が異なっており、__dict__
で全てのクラスインスタンスを共有するようにしています。これはBorg、またはMonostateと呼ばれます。
class Borg(object):
_state = {}
def __new__(cls, *args, **kwargs):
ob = super().__new__(cls, *args, **kwargs)
ob.__dict__ = cls._state
return ob
print(Borg() == Borg())
# False
Borgクラスを複数作ると別のインスタンスができます。それでもSingletonの代替として使えるのは複数のインスタンスが1つのように振る舞うからです。Singletonは1つのインスタンスを保証しますが、それ以上に重要なのは振る舞いを1つであることを保証することです。ですので、振る舞いの唯一性を保証できるBorgはSingletonの代替となります。
ですが、この方法にも罠があります。サブクラスに置ける問題を解決することができますが、サブクラスの実装に依存してしまっています。例えば、サブクラスで.
で属性を取得するときの処理をする__getattr__
をオーバーライトしていた場合はBorgはうまく動作しなくなってしまいます。
簡潔な実装法
ここまで様々な方法を紹介しましたが、最も簡単な方法は関数を持ったモジュールを用意することです。なぜなら、Pythonのモジュールは元々Singletonオブジェクトだからです。上記の実装方法が必要になるのはJavaのようなフレームワークを使っている場合です。ですので、特別の事情がない限りはSingletonパターンは必要がないと思います。
参考
- エキスパートPythonプログラミング改訂2版
- アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技