The Singleton pattern
The Singleton pattern is one of the patterns that finds its place in the book by Gang of Four, and which can have various uses where all we want is a class to have a single instance throughout an application.
The Singleton pattern enforces that a class will have only one instance that will be used by any of the components/modules inside an application. This kind of enforcement can be useful when we want to control the access to a resource using only one object. These type of resources can be log files, databases, crash-handling mechanisms, and so on.
In most of the OOP-based languages, to implement the Singleton pattern, the first step is to make the class constructor private and then use a static method inside a class to return the same instance whenever some part of the code needs to use the functionality of the class. In Python, we do not have the functionality of having a private constructor, so, how can we implement the Singleton pattern?
The answer to this question lies with the use of metaclasses. We can use metaclasses to implement the Singleton pattern. Let's take a look at how we can implement the Singleton pattern by using metaclasses.
For an example, let's take our BugZot application. In our BugZot application, we will require the functionality that the different modules should be able to access the database for a various set of reasons, such as writing a new record for a bug, fetching the bug records, creation of new users, and modification of user permissions. Now, this can be done in two ways; either we can create a database class and allow the modules to instantiate a unique database class object by themselves and use it, or we can instantiate the database class object once the application starts and, whenever some module tries to instantiate the database class object for their own use, we can provide the same object that we instantiated. In the first approach, we will unnecessarily create a lot of objects wasting the precious database connection resources, while also causing an unnecessary increase in memory usage, due to an increased number of objects. So, let's take a look at how we can implement the Singleton pattern here to solve our problem.
Let's first create a file, Singleton.py, under the meta directory of our application. This file will hold the implementation for our Singleton metaclass, which we will be using to create Singleton classes later in our application whenever we require them:
class SingletonMeta(type):
_instance_registry = {} #Build an instance registry which tracks the different class objects
def __call__(cls, *args, **kwargs):
if cls not in cls._instance_registry: # check, if the class has already been instantiated
cls._instance_registry[cls] = super().__init__(*args, **kwargs)
return cls._instance_registry[cls]
In this example, we provided a definition for our Singleton metaclass, which derives from the type metaclass.
Inside the class, we first initialize a dictionary, which will hold the records of the objects of individual classes that use the Singleton class as their metaclass. The next thing we declare in our metaclass is the __call__ magic method, which takes class name, its arguments, and keyword arguments as parameters. Before discussing what exactly the __call__ magic method does, let's first see what we are doing inside the method.
Once this method is executed, we first check if we already have an initialized object of the class by checking for the records under the _instance_registry dictionary. If we don't find the instance of the class already initialized, we initialize a new instance of the class and save it in the dictionary. Following that, we return the initialized instance.
What this means in a practical scenario is, when the class is first initialized, a new instance will be created, a record of it will be stored in the _instance_registry dictionary, and then the instance will be returned. Any further attempt to create a new instance will always end up with returning the same object. And that is what the Singleton pattern aims to achieve.