×

Search anything:

Singleton Pattern in Python

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

In this article, we have explored the concept of Singleton Design Pattern in Python. Singleton Pattern is one of the most common design patterns.

Objectives

  • Define Design Patterns and list the types.
  • Define Singleton Pattern
  • Implement Singleton Pattern and apply it in various applications and systems using Python and Similar OOP Languages.
  • Write reusable code using Singleton Pattern.

What are Design Patterns

Design patterns are reusable formatted best practices that programmers can use to solve common problems when designing applications or systems. They can be classified into Creational Patterns Structural Patterns, Behavioural Patterns, and Concurrency Patterns.

Singleton Pattern

It is a type of creational pattern dealing with object creation. The pattern restricts the instantiation of a class to one single instance. It is used when only one object is needed to coordinate actions across applications. Singleton pattern is useful in logging, driver objects, caching, and thread pool. Python module made use of singleton pattern. Python checks whether a module has been imported, if imported it returns the object for the module, and if not it creates it.

Singleton-Class.png

UML Diagram of Singleton class

The ideology behind the creation of the singleton class is as follows:

  1. We will allow a singleton class instance to be created on the first attempt.
  2. If an instance already exists, we will return the previously created instance of the singleton class.

Implementing this can be done in __ new __ python magic method. The code is as follows:

class Singleton:
    def __new__(cls, *args):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance
s = Singleton() # newly created instance
s1 = Singleton() # returned the previously created instance - s
print(f"{s=}\n{s1}")

In the preceding snippet __ new __ magic method was overridden to control objects creation. s object got created with the __ new __ method while s1 returns the previously created object instance. The logic behind the snippet is that hasattr builtin function checks if the object class to be created has an instance attribute, if it doesn't, a new object will be created, and if it does have instance attribute, the previously created object will be returned. You'll also notice that the id of both objects are the same. The results are shown below:

s=<__main__.Singleton object at 0x7fe9df1986d0>
s1=<__main__.Singleton object at 0x7fe9df1986d0>

The above implementation is the Gang of Four(GoF) implementation of the Singleton pattern. There is also another way we can declare a Singleton class. The idea behind this is that objects can be created as many times as we want but they should all share the same states and behaviors. This is what we call the Borg or Monostate pattern.

Python uses __ dict __ to store the state of every object of a class and that is what we will manipulate to share the state across all objects of a class. Below is the implementation.

class Borg:                                                                   
    __shared_state = {'borg': 'Monostate'}                                    
    def __init__(self):                                                       
        self.x = 'Design Pattern'                                             
        self.__dict__ = self.__shared_state                                   
        pass
b = Borg()
b1 = Borg()
b.x = 2100
print(f"b {b.__dict__}, {b.x=}")
print(f"b1 {b1.__dict__}, {b1.x=}")

The results of the preceding snippets are given below:

b {'borg': 'Monostate', 'x': 2100}, b.x=2100
b1 {'borg': 'Monostate', 'x': 2100}, b1.x=2100

Singleton and metaclasses

A metaclass is a class of class. It is through metaclass that programmers are able to create their classes from python predefined classes. As a matter of fact, everything in python is an object.

As metaclass has more control over class creation and object instantiation, it can be used to create Singletons. Metaclasses in python override the __ new __ and __ init __ or __ call __ magic methods. Below is the sample code for Singleton implementation with metaclasses.

class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
class Logger(metaclass=MetaSingleton):
    pass
log1 = Logger()
log2 = Logger()
print(f"{log1=}\n{log2=}")

The results of the above snippets are shown below:

log1=<__main__.Logger object at 0x7f5e89a79450>
log2=<__main__.Logger object at 0x7f5e89a79450>

As excepted the logger instances are the same object(same id i.e pointing to the same object).

Real World example of Singleton pattern

We will now consider an example of a cloud service that involves multiple read and write operations on the database. What we need to singleton here is the database because the web app will call an API which eventually operates on the database. We will make use of sqlite3, it comes bundled with python. The code is shown below.

import sqlite3
class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
class Database(metaclass=MetaSingleton):
    connection = None
    def connect(self):
        if self.connection is None:
            self.connection = sqlite3.connect('db.sqlite3')
            self.cursorobj = self.connection.cursor()
        return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print(f"{db1=}\n{db2=}")

The results are as follows:

db1=<sqlite3.Cursor object at 0x7f5e88502340>
db2=<sqlite3.Cursor object at 0x7f5e88502340>

From the above, you can see that the cursorobj is the same for all objects created from the Database class, including the Database class(Gof Singleton).

Drawbacks of Singleton pattern

  • Global variables can be changed by mistake.
  • Multiple references may get created to the same object
  • Classes are tightly coupled, so changing one class inadvertently impacts the other class
David Oluwafemi

David Oluwafemi

I love meeting new people. I love to teach others and likewise learn from others. Since learning never ends. So I will learn till the very end. I am proficient in Python, JavaScript, and Shell Script.

Read More

Improved & Reviewed by:


OpenGenus Tech Review Team OpenGenus Tech Review Team
Singleton Pattern in Python
Share this