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

class MyClass(object):

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:
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True
    return Single

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

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