Python Design Patterns - SingleTon


This SingleTon pattern is very useful pattern when we need global instance of any service or class. This pattern is used to implement a class which returns only one instance every time when we try to create new instance.

Pattern Type - Creational Design Pattern

Benefits -

  • One and only one object is created
  • Object with global access to whole program
  • Controlling concurrent access to shared resources.

Now to implement this pattern to achieve SingleTon instance, we have 4 solutions and all of them are explained below one by one so, you can use any of them according to your programming style and your project/product.

Solution - 1 Using new method. new method is used to create new instance when we try to access this class.

class SingleTon(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Logger, cls
                    ).__new__(cls, *args, **kwargs)
        return cls._instance

Usage - To use this method to make your class SingleTon, You just need to copy new method to your class.

Method - 2 Monostate(Borg) pattern

Alex Martelli gave different solution to achieve this. Solution is called Monostate(‘Borg') pattern. This method returns new instance every time but they share same properties(state) so ultimately it serves same purpose.

This solution is based on special variable of class dict. All properties of the object are stored in this special dict variable of class object. So we need to isolate dict variable for all the objects.

class Borg(object):
    _shared = {}
    def __init__(self):
        self.__dict__ = self._shared

class SingleTon(Borg):
    def __init__(self, arg):
        Borg.__init__(self)
        self.val = arg
    def __str__(self): return self.val

Usage - SingleTon itself is an example so if you want to use this method then just need to rename SingleTon class name to your class name as following.

class Logger(Borg):
    def __init__(self, arg):
        Borg.__init__(self)
        self.val = arg
    def __str__(self): return self.val

Next two methods to achieve singleton pattern are very useful. Let's look at them.

Method - 3 Create decorator to make any class singleton using call method.

class SingletonDecorator(object):
    def __init__(self, klass):
        self.klass = klass
        self.instance = None
    def __call__(self, *args, **kwargs):
        if self.instance == None:
            self.instance = self.klass(*args,**kwargs)
        return self.instance

Usage - Let's use above decorator class and look how it can be used. Following is the example of usage -

@SingletonDecorator
class A(object):
    def __init__(self):
        self.x = 0

print("Creating first object of class", A)
a1 = A()
print('a1.x', a1.x)

print("Creating second object of class", A)
a2 = A()
print('a2.x', a2.x)

print('Changing a2.x to 10')
a2.x = 10
print('a2.x', a2.x)

print('a1.x without changing', a1.x)

# output
"""
Creating first object of class <__main__.SingletonDecorator object at 0x7f61c5b25a20>
a1.x 0
Creating second object of class <__main__.SingletonDecorator object at 0x7f61c5b25a20>
a2.x 0
Changing a2.x to 10
a2.x 10
a1.x without changing 10
"""

Method - 4 Create metaclass to make any class singleton similar as previous method using __call__ method and type.

class SingletonMeta(type):
    __instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls.__instances:
            cls.__instances[cls] = super().__call__(*args,**kwargs)
        return cls.__instances[cls]

Usage - Let's use metaclass and look how it can be used.

class B(metaclass=SingletonMeta):
    def __init__(self):
        self.x = 0

print("Creating first object of class", B)
b1 = B()
print('b1.x', b1.x)

print("Creating second object of class", B)
b2 = B()
print('b2.x', b2.x)

print('Changing b2.x to 10')
b2.x = 10
print('b2.x', b2.x)

print('b1.x without changing', b1.x)

# output
"""
Creating first object of class <class '__main__.B'>
b1.x 0
Creating second object of class <class '__main__.B'>
b2.x 0
Changing b2.x to 10
b2.x 10
b1.x without changing 10
"""

Now you can use any method from above methods according to your needs in your work.

Drawbacks -

  • Multiple references are created to same object
  • Global access of the object can leave developer confused sometimes from where the value is changed as the same object is used multiple places.

Thanks for reading this article. If you have any comments or questions please let me know in comment section.

blog comments powered by Disqus