Skip to content

Singleton

Singleton is a design pattern that describes how to create an object that can be constructed at most once in a given application.

Pros and cons

Singleton is quite a controversial pattern because it violates the principle of Single Responsibility. Moreover, since the same instance is used globally in the application, such a singleton can create many dependencies between objects. Ultimately, code using a singleton can become unreadable and difficult to fix in the event of application errors (who changed its state, when and where?). However, a singleton also has its advantages. It can save the memory used by the application and save time if creating the object takes a relatively long time.

Design

The first, naive singleton implementation uses a class method that returns a single instance:

class Singleton:
    __instance = None

    @classmethod
    def get_instance(cls):
        if not cls.__instance:
            cls.__instance = Singleton()
        return cls.__instance

    def __init__(self):
        if not self.__instance:
            print("The common instance has not been created - I am not a Sigleton!")
        self.val = 0

    def function(self):
        print(self.val)

a = Singleton.get_instance()
a.function()
a.val = 10
a.function()

b = Singleton.get_instance()
b.function()

The problem with this implementation occurs when we instantiate the Singleton class directly without using the getInstance () method. The created instance will not be a singleton! Additionally, in such an implementation, the functionality of the instance is combined with the "mechanics" of the singleton.

A common solution to these problems is a nested class that collects the functionality of an instance and the use of the __new__ method:

class Singleton:
    class __Singleton:
        def __init__(self):
            self.val = None

        def __str__(self):
            return repr(self) + self.val

    __instance = None

    def __new__(cls):
        if not Singleton.__instance:
            Singleton.__instance = Singleton.__Singleton()
        return Singleton.__instance

x = Singleton()
x.val = 'test01'
print(x)
y = Singleton()
print(y)
y.val = 'test02'
print(y)

Using a decorator effectively and elegantly separates the singleton class implementation from the "mechanics":

def Singleton(class_):
    __instances = {}
    def get_instance(*args, **kwargs):
        if class_ not in __instances:
            __instances[class_] = class_(*args, **kwargs)
        return __instances[class_]
    return get_instance

@Singleton
class FirstClass:
    def __init__(self):
        self.val = 0

@Singleton
class SecondClass:
    def __init__(self):
        self.val = 0

a = FirstClass()
print(a.val)
a.val = 10
b = FirstClass()
print(b.val)

c = SecondClass()
print(c.val)
c.val = 20
d = SecondClass()
print(d.val)

In this solution, we can easily turn any class into a singleton.

The most recommended Pythone implementation of a singleton is based on the metaclass.

class SingletonType(type):
    _instances = {}

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

class SingletonClass(metaclass=SingletonType):
    pass

x = SingletonClass()
y = SingletonClass()

print(x == y)

The metaclass is called automatically when a class declaration using the metaclass ends, in other words: - if there is no metaclass keyword, type () is called after base classes (exactly the __call__ method from type) - if there is a metaclass keyword, the class assigned to it is called (its __call__)

Alex Martinelli noticed (http://www.aleax.it/Python/5ep.html), that the need to use a singleton often results from sharing only data and he proposed a solution based on the common __dict__ attribute:

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

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

x = Singleton('apple')
print(x)
y = Singleton('pear')
print(y)
z = Singleton('plum')
print(z)
print(x)
print(y)

It is worth adding here that to obtain the singleton functionality, i.e. one instance of data and operations on them, we can simply collect them in a separate module. Python module is imported only once, global variables defined in it can be treated as class attributes and its functions as its methods. Purists will say that it's not a class, but as Python's philosophy says "simple is better than complex".