Wednesday, August 24, 2005

Design Patterns - Modelling the Singleton in Python

Singleton is a design pattern that seems to interest everyone, especially in the Python world.

I was doing a Google search on the ways in which Python implements the Singleton design pattern.

The results showed that in doing this in Python, you are limited only by your imagination. Unlike C++ or Java, you are not limited to a certain strategy of modeling the Singleton in Python.

I thought it was a good idea to gather the different Singleton solutions in Python and post it in a single post (pun intended) here. In this post, I list seven ways of modelling the Singleton in Python which looks elegant to me. I am not including some overly verbose or cryptic solutions which you will find if you perform the Google search above.

Though I have no preference for any particular solution, I have ordered them in the order of what I think is the least elegant solution, to the most elegant one. Of course this is purely personal! :-)

A word of caution: Except for the Borg solution, the rest of them will work only with new style classes. Also note that some solutions are exactly the same, though they look different. I have explained them as we go along.

The most basic solution overrides the __new__ method in an outer class, returning an instance of an inner class as the Singleton. Here it is:
`class Singleton1(object):    """ Singleton by overriding __new__ and using an innerclass by using new style classes """   class __Singleton(object):       pass   __instance = None     def __new__(cls):       if not Singleton1.__instance:           Singleton1.__instance = Singleton1.__Singleton()       return Singleton1.__instance`
Here is this solution in action:
`s1=Singleton1()print s1s2=Singleton1()print s2s3=Singleton1()print s3<__main__.__Singleton object at 0x009F1390><__main__.__Singleton object at 0x009F1390><__main__.__Singleton object at 0x009F1390>`
Clearly the drawback with this solution is that it is not a Singleton in its true sense. The Singleton class does not return an instance of itself, but an instance of an inner class. In other words, what appears to be the Singleton class is actually a class wrapper around an inner class, which is the actual Singleton. Not very elegant.

The next solution fixes this problem. It works directly with the guts of the class by accessing the classe's dictionary.

class Singleton2(object):

""" Singleton by using new style classes """
`    def __new__(cls):        if not '_the_instance' in cls.__dict__:            cls._the_instance = object.__new__(cls)        return cls._the_instance`
All right. Is there something magical about the _the_instance attribute ? Nothing. So why can't we replace it with a direct class level attribute? Yes, you can though there is not much difference in both technically. However, it looks like a kind of combination of the first solution with the second one (which it is not), so here it is for illustration purposes.

class Singleton3(object):
`    """ Singleton by using direct class attributeaccess without using cls.__dict__. This mightlook different from Singleton2, but in factit is the same. """    __instance = None       def __new__(cls):        if not Singleton3.__instance:            Singleton3.__instance = object.__new__(cls)        return Singleton3.__instance`
All right. Enough of fooling around with classes directly. Can't we do this by using metaclass magic? Looks like you can. And with most metaclass solutions, it seems to be somehow more elegant than directly putting the logic inside the class!

Here is the solution from Bruce Eckel's Thinking in Python.
`class SingletonMetaClass(type):def __init__(cls,name,bases,dict):super(SingletonMetaClass,cls)    .__init__(name,bases,dict)original_new = cls.__new__def my_new(cls,*args,**kwds): if cls.instance == None:   cls.instance =         original_new(cls,*args,**kwds) return cls.instancecls.instance = Nonecls.__new__ = staticmethod(my_new)class Singleton4(object):__metaclass__ = SingletonMetaClass`
The idea is to override the __new__ method of the object's class right in it's metaclass's __init__ method. This is done the first time the object is created. The overrided __new__ works quite similar to the one in Singleton1,Singleton2 and Singleton3. Thereafter, everytime you create an object of Singleton4, it will call the __init__ in its metaclass where the magic happens.

The elegancy of this solution comes from the fact that, the extra code for the class is just one line, where we assign the __metaclass__ attribute. All the magic resides in the metaclass, which allows one to quickly convert his class to a Singleton by adding just this one line.

NOTE: In fact the metaclass solutions for Singletons (or any other patterns for that matter) are not class scoped solutions, but type scoped ones. It might take some time to wrap your head around that, if you come from a C++ background.

But does this look a bit cryptic? Well, I should say yes since it took some time for me to figure out what is happening here. Apparently, you don't need to take all that trouble to get it right. Here is the above solution re-written, but without the inner function and all that.

class SingletonMetaClass(type):
`    """ Singleton using metaclasses by overridingthe __init__ method, 2nd version. """    def my_new(cls, *args, **kwargs):        if not cls.instance:            cls.instance = object.__new__(cls)        return cls.instance               def __init__(cls, name, bases, dct):        super(SingletonMetaClass, cls).__init__(name, bases, dct)        cls.instance = None        cls.__new__ = cls.my_newclass Singleton5(object):    __metaclass__ = SingletonMetaClass`
The above solution is a re-write of Singleton4, but lesser cryptic and more readable.

Is that all you can do with metaclasses and the Singleton in Python? The answer is No. Looks like there is a much more elegant way of doing this using metaclasses. It is done by overriding
the __call__ method in the metaclass, instead of the __init__ method. This solution is the ASPN Python Cookbook Recipe #412551 by Daniel Brodie. I am just copying it here.

class SingletonMetaClass(type):
`    """ Singleton using metaclasses by overriding the __call__ method.          Original code courtesy from ASPN Python Cookbook recipe number          412551 """    def __init__(self, *args):        type.__init__(self, *args)        self._instances = {}    def __call__(self, *args):        if not args in self._instances:            self._instances[args] = type.__call__(self, *args)        return self._instances[args]class Singleton6(object):    __metaclass__ = SingletonMetaClass`
Before I conclude, I should include one of the most ingenious methods of doing the Singleton in Python, created by Alex Martelli. This is the so-called Borg non-pattern. This and the concept of non-patterns is discussed in detail here.

The Borg is unique in that it re-defines the problem Singleton is trying to solve. Instead of trying to ensure that the unique instance maps to a unique memory location, Borg ensures that the state of the various instances are shared and hence the various instances are in effect, the same. In other words, Borg focuses on object equivalence instead of object identity which is what Singleton offers.

Here is the Borg non-pattern, applied to the Singleton problem.

class Singleton7:
`    """ Alex martelli's Borg non-pattern. Not exactly    a singleton. Focus on equivalence of state rather than the    uniqueness of the Singleton instance """    __shared_state = {}    def __init__(self):        self.__dict__ = self.__shared_state`
Here is the Borg non-pattern in action.
`s1=Singleton7()# Set s1's states1.x = 100print s1.__dict__s2=Singleton7()print s2.__dict__s3=Singleton7()print s3.__dict__{'x': 100}{'x': 100}{'x': 100}`
Well, according to me, that is the most elegant one. Instead of solving the Singleton problem directly, it solves the problem that the Singleton is trying to solve, which is that of ensuring
unique state across instances.

That is the end of my first post on Python and Design patterns. I hope to add more in the future, on the rest of the Gang of four Design Patterns.

johnjackson31924108 said...

I read your blog, and i thought it was rather cool. check out My Blog