いろいろ論議がでそうなデザインパターンの1つである「Singleton」をPythonでやる方法を見ていきたいと思います!
Singletonとは
Singletonは、オブジェクト指向のプログラムにおけるデザインパターンの1つです。作成されるクラスのインスタンスが、1つしか生成されないことを保証する仕組みです。アプリケーション全体で、絶対に1つにしないといけない仕組みの実装に使用されています。
これはいまいちかも?
継承されることを想定して考えると、コンストラクタ(厳密には違います)の「__new__」で実装するのはいまいちかもしれません。
だいたいこんな感じのコードかと思います。
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = object.__new__(cls, *args, **kwargs) return cls._instance
継承されたクラスで、もし「__new__」をオーバーライドされてしまったら、基底クラスの「__new__」を呼び出してもらう必要が出てきます。PySideのクラスを継承し「__init__」をオーバーライドしたら、基底クラスの「__init__」を実行しないといけないのと似ていますね。
メタクラス
継承などを踏まえると、Pythonでは、メタクラスが便利だと思います!メタクラスとは、簡単にいうと、クラスの動きを定義するためのクラス、、、っと言ったところでしょうか。
細かい説明をするのもアレなので、まずはコードを御覧ください。
class SingletonType(type): _instance = None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) return cls._instance
まずは、基底クラスの「type」から見ていきます。
typeって?
typeを使って、obj の型を取得するのに利用したことは1度位はあると思います。実は type にはもう一つ役割があります。それが「type(name, bases, dict)」です。
これは、第1引数にクラス名、第2引数に親クラスのタプル、第3引数にメソッドや属性を定義した dict を渡すと、「クラスを動的に定義することが可能」というものです!!
Pythonのクラスは、「type」にクラス名やらを渡してクラスを生成している、、、っという仕組みなわけですね。
Pythonでは「クラスもオブジェクト」と聞いたことがあるかもしれませんが、この「type」のインスタンスなのです。以下のコードを試してみると、「あ、ホントダ」っと思っていただけると思います(`・ω・´)ゞ
class TestClass(object): pass print isinstance(TestClass, type)
メタクラスっと書くとわかりにくいかもしれませんが、今回のSingletonの実装は、クラスを生成している「type」をカスタマイズして、インスタンス生成を制御して一回だけにする!っというものです(*´ω`*)b
__call__って
インスタンスが「関数として呼び出される」と実行される特殊メソッドです。詳しくは後述いたしますが、「クラス名()」のタイミングで呼び出されます。
メタクラスを使う
先程作成した「SingletonType」を使って、クラス「Singleton」を作っていきます。
class SingletonType(type): _instance = None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) return cls._instance class Singleton(object): __metaclass__ = SingletonType
メタクラスを使用するには、クラス内に「__metaclass__」を定義します。このクラス「Singleton」が「定義されたタイミング」で、メタクラス「SingletonType」の「__new__」が実行されます。
大事なので、もう一度いいます。「定義されたタイミング」で、メタクラス「SingletonType」の「__new__」が実行されます。
クラス「Singleton」を使うには、今まで通り「obj = Singleton()」とすればOKです。この時、メタクラスの「__call__」が実行されます!
「__call__」には、まずインスタンスが作成されているか調べていましたね!もし作成されていなければ、基底クラスの「__call__」を実行してクラスのインスタンスを作成します。
もし作成済みであれば、過去に作成したインスタンスを返します。
確認
オブジェクトがちゃんと1つになっているか確認してみます!インスタンスが同じものであるか確認するには「id」が便利です!
class SingletonType(type): _instance = None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) return cls._instance class Singleton(object): __metaclass__ = SingletonType print id(Singleton()) == id(Singleton())
Trueが表示されればバッチリです!
念のため、複数のクラスで使用して確認してみます!
class SingletonType(type): _instance = None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) return cls._instance class SingletonA(object): __metaclass__ = SingletonType class SingletonB(object): __metaclass__ = SingletonType print id(SingletonA()) == id(SingletonA()) print id(SingletonB()) == id(SingletonB()) print id(SingletonA()) == id(SingletonB())
まずSingletonAのインスタンスが1つか確認します。次に、SingletonBのクラスが1つか確認します。最後に、SingletonAのインスタンスとSingletonBのインスタンスが混合していないか確認します。True、True、Falseと表示されればバッチリです!
最後に、クラス「Singleton」を継承したクラスを2つ作り確認します。実際の運用を考えると、メタクラス「SingletonType」は内部利用で、クラス「Singleton」を継承すれば、「シングルトンになるよ!」っという感じです。
class SingletonType(type): _instance = None def __call__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) return cls._instance class Singleton(object): __metaclass__ = SingletonType class TestA(Singleton): pass class TestB(Singleton): pass print id(TestA()) == id(TestA()) print id(TestB()) == id(TestB()) print id(TestA()) == id(TestB())