Monday, January 26, 2015

Python Singleton pattern - generic approach. Part I

Today I was puzzled with implementing Singleton pattern in Python. I wanted instrumentational approach, i.e. not to write your class as singleton from ground up, but to take any existing class and turn it into singleton.

Simple as it may seem, I've ended with two part blog post series on the subject. This part talks about decorator approach and the next part talks about classmethod approach. Enjoy.

Decorator approach

Stackoverflow hinted a decorator approach, but that example breaks Python object model. Particularly, considering:

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass(object):
    pass

Then the following would reasonably fail:

c = MyClass()
isinstance(c, MyClass)

TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
Since in fact after decoration MyClass is a function and not a class.

To fix this, we'll rewrite decorator to return class instead. And to make sure that inheritance check works, we'll inherit from decorated class during decoration process.

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None
        
        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance
                       
        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True
    return Single

@SingletonClass
class MyClass(object):
    pass
Now isinstance does the right thing:
>>> c = MyClass()
>>> isinstance(c, MyClass)
True

We are sort of double-cheating here, since even c.__class__ == MyClass is true. I say cheating because both of them are actually <class 'Single'> and original MyClass class is lost during decoration.

And a final touch is to copy a doc string of course :)