Going further with our series of design patterns applied in automation, in this article, we will cover one of the simplest creational patterns – Singleton. To get a better understanding of it, we will go step by step through four ways of implementation, so we’ve split the tutorial into two parts. First, we will address the classical Singleton implementation and the lazy instantiation one and in the second part, we will use the metaclass approach and the Python decorators.
Before the actual implementation, let’s review the Singleton definition. It is aimed to deliver only one object of a given type and provides a global access point. In our ‘Selenium webdriver’ context, it allows us to control the concurrent connection with the browser driver.
As a requirement, please consider Python 3.
The classical Singleton
Keep in mind
- Override the __new__ dunder method to control the object creation so that we check if the object it’s already available
- ‘hasattr’ method checks if the class already has an instance
class ClassicalSingleton(object): """ Singleton class based on overriding the __new__ method """ def __new__(cls): """ Override __new__ method to control the obj. creation :return: Singleton obj. """ if not hasattr(cls, 'instance'): cls.instance = super(ClassicalSingleton, cls).__new__(cls) return cls.instance @staticmethod def get_driver(): """ :return: Selenium driver """ return webdriver.Chrome()
d1 = ClassicalSingleton() d2 = ClassicalSingleton() print(d1, d2) #The output will be <src.singleton.singleton_classical.ClassicalSingleton object at 0x10b969128> <src.singleton.singleton_classical.ClassicalSingleton object at 0x10b969128>
Singleton based on lazy instantiation
To keep in mind: Lazy instantiation assures that the object is created when it’s mandatory
class LazyInstSingleton: __instance = None def __init__(self): if LazyInstSingleton.__instance is None: print('__init__ method called') else: raise ValueError("An instantiation already exists!", self.get_instance()) webdriver.Chrome() @classmethod def get_instance(cls): if cls.__instance is None: cls.__instance = LazyInstSingleton() return cls.__instance
# Below we will execute the instantiation
s = LazyInstSingleton() # class initialization
print("Obj created", LazyInstSingleton.get_instance()) # Object creation
d = LazyInstSingleton() # initialize again the class, so error will be raised
#The output will be: ValueError: ('An instantiation already exists!', <__main__.LazyInstSingleton object at 0x1108ecc50>)
In conclusion, we’ve covered two implementation methods of the singleton pattern in Python. For both of them, there is a downside, first following the classical singleton implementation, the method
__new__ could be also overridden by a child class and using the lazy instantiation, it leads to an ugly code due to the necessity to call the
get_instance method in order to achieve the pattern. For a better approach, I suggest following the next article, where we will cover the singleton implementation based on metaclasses and decorators.
The source code of this article could be also found here