Pythonでシングルトン!!

いろいろ論議がでそうなデザインパターンの1つである「Singleton」をPythonでやる方法を見ていきたいと思います!

MayaなどのCGソフトに合わせて、Python2系で書いています。

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())