Design Pattern: Singleton Pattern in Python

The Singleton Pattern is used to put restrictions on object creation, so that we can instantiate a single object of the class. Every time we try to instantiate a class, it will return the same object.

NOTES

In this article, we are discussing the implementation of the Singleton Pattern in Python.

See the Singleton in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Singleton Pattern-

Implementation

WARNING

The following implementation methods are for Python only. As the Singleton pattern implementation is a little tricky, that’s why the implementation is different in different programming languages.

Check the Singleton implementation in other programming languages in the “Other Code Implementations” section.

For the Singleton pattern implementation, we need a process that prevents the instantiation of a class more than once. Also, it should prevent cloning.

NOTES

As we need to ensure the single(same) object each time, for that we need some capability to maintain a global state in the Python code.

We have discussed two(2) implementation methods here, that can be used to implement the Singleton pattern in Python.

Method #1: Using Metaclass

NOTES

A metaclass in Python is known as the “class of classes“, which defines how a class(another class) will be created, managed and behave.

We have defined a metaclass named SingletonMeta.
We have defined a class-level dictionary named _instances, to store the generated class instances.
The __call__ method defines what happens when a class instance is generated by the metaclass(SingletonMeta).
In the __call__ method we are checking if an instance of the provided class already exists in our _instances dictionary. If an instance already exists then we return that instance, else we create a new instance, save it in the _instances dictionary and finally return that.
If an instance does not already exist then we are calling the parent “type” class’s __call__ method to create the instance.
Finally, we have created a Singleton class that uses the metaclass SingletonMeta.

NOTES

We could have used the “_instances” attribute to store a single object reference, instead of using a dictionary. But that would make it work for only one singleton class.

As we have used a dictionary, so we can use the same “SingletonMeta” class to implement the Singleton pattern for other classes. The “_instances” dictionary will store instances of all the classes that are using “SingletonMeta“.

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            # Create the singleton instance if it doesn't exist
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
    def __init__(self, some_val: str) -> None:
        self.some_val = some_val

    def print_details(self) -> None:
        print(f"some val: {self.some_val}")


# Usage:
if __name__ == '__main__':
    # Creating the first singleton instance
    singleton_instance = Singleton("abc")
    singleton_instance.print_details()
    
    # Trying to create another instance
    another_instance = Singleton("changed val")
    another_instance.print_details()
    
    # Checking if both instances are the same
    print(singleton_instance is another_instance)
Python

Output:

some val: abc
some val: abc
True

Method #2: Using Global Object Pattern

Create a static variable named “_instance“. This is used to store object instance.
In the “__new__” we are controlling the creation of the object.
If there is no existing instance, then create a new one using “super(Singleton, cls).__new__(cls)” and save it in “_instance“.
If an instance already exists then return that instance(instead of creating).
In the “__init__” method we are setting values and also setting a variable named “initialized”. The “initialized” variable is used to check if any instance is already created/set or not. If already not created then set the properties of the class.

NOTES

When we create a new instance, the “__new__” method is called first. Here we are creating an instance(if it does not already exist), and saving it in the “_instance” static variable.

__init__” method is called after “__new__“. We are setting attribute values using this method.

from typing import Optional


class Singleton:
    _instance: Optional["Singleton"] = None  # This is the global object, shared across the module

    def __new__(cls, some_val: str) -> "Singleton":
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

    def __init__(self, some_val: str) -> None:
        if not hasattr(self, "initialized"):
            self.initialized = True
            self.some_val: str = some_val

    def print_details(self) -> None:
        print(f"some val: {self.some_val}")


# Demo Usage
if __name__ == "__main__":
    # Creating the first singleton instance
    singleton_instance: Singleton = Singleton("abc")
    singleton_instance.print_details()

    # Trying to create another instance
    another_instance: Singleton = Singleton("changed val")
    another_instance.print_details()

    # Checking if both instances are the same
    print(singleton_instance is another_instance)
Python

Output:

Following output will be generated-

some val: abc
some val: abc
True

Examples

Let’s take a look at the examples and use cases that require Singleton pattern implementation.

Example #1: Database Connection

The following example demonstrates the use of the Singleton pattern for managing a database connection. It ensures that only one instance of the connection is created, no matter how many times we try to instantiate the class.

This implementation ensures a consistent connection, through the application lifecycle.

NOTES

We are using the “Global Object Pattern” for this implementation.

DB Connection Singleton Class

Create a class named “DBConnectionSingleton“.
Declare a class variable named “_db_instance“, which will store the class instance. We have initialized it with “None“.
In the “__new__” method check if “_db_instance” is already set or not.
If “_db_instance” is not already set then create a new instance using “super(DbConnectionSingleton, cls).new(cls)” and save it in “_db_instance“.
If “_db_instance” already exists then return that.
In the “__init__” method check if the instance is new or pre-existing, using the attribute “initialized“. If it is a new instance then set the attributes sent for database connection.
from typing import Optional


class DbConnectionSingleton:
    _db_instance: Optional["DbConnectionSingleton"] = None

    def __new__(
        cls, url: str, port: str, username: str, password: str
    ) -> "DbConnectionSingleton":
        if cls._db_instance is None:
            cls._db_instance = super(DbConnectionSingleton, cls).__new__(cls)
        return cls._db_instance

    def __init__(self, url: str, port: str, username: str, password: str) -> None:
        if not hasattr(self, "initialized"):
            self.initialized = True

            self.url: str = url
            self.port: str = port
            self.username: str = username
            self.password: str = password

    def print_connection_details(self) -> None:
        print(f"URL: {self.url}")
        print(f"Port: {self.port}")
        print(f"Username: {self.username}")
        print(f"Password: {self.password}")

    def execute_query(self, query: str) -> None:
        print(f"Executing query: {query}")
Python

Demo

Create an instance of “DbConnectionSingleton” with some values of host, port, and user info.
Create another instance of “DbConnectionSingleton”.
Compare those 2 instances.
# Demo usage
# Main function for demo
def main() -> None:
    # First connection attempt
    db_connection: DbConnectionSingleton = DbConnectionSingleton(
        "localhost", "5432", "postgres", "secret*pass"
    )
    print("First Connection Details:")
    db_connection.print_connection_details()

    # Second connection attempt (should return the same instance)
    second_db_connection: DbConnectionSingleton = DbConnectionSingleton(
        "192.168.55.55", "1234", "postgres2", "secret*pass2"
    )
    print("\n\nSecond Connection Details:")
    second_db_connection.print_connection_details()

    # Checking if both references are pointing to the same instance
    print(
        f"\nIs the first connection same as the second? {db_connection is second_db_connection}"
    )


if __name__ == "__main__":
    main()
Python

Output

We can see, that the 2 instances have the same info, and the information provided the first time is persisted.

First Connection Details:
URL: localhost
Port: 5432
Username: postgres
Password: secret*pass


Second Connection Details:
URL: localhost
Port: 5432
Username: postgres
Password: secret*pass


Is the first connection same as the second? 
True
Plaintext

Example #2: Setting

The following example demonstrates the singleton pattern implementation for a class that stores the settings of an application. No matter where we call the class, we want the same setting instance. There is no need to create a new instance each time, as for the application runtime lifecycle the setting will remain the same.

We are mainly using the setting to store values as key-value pairs.

NOTES

We are using the “Metaclass” for this implementation.

Singleton Metaclass

Create a metaclass named “SingletonMeta” which inherits from built-in metaclass “type”.
Define a dictionary named “_instances” of type dictionary. This will be used to store the instances, created through the metaclass.
In the “__call__” method, which is responsible for creating new instances, create the instances using “super().call(*args, **kwargs)” and save them in the dictionary “_instances”.
from typing import Any, Dict


class SingletonMeta(type):
    """A metaclass that ensures only one instance of a class is created."""
    _instances: Dict[type, Any] = {}

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

Singleton Class

Define a class named “setting”, that uses metaclass “SingletonMeta”.
Initialize attribute “_props” in the “__init__” method.
Define getter and setter methods.
from typing import Any, Dict

class Setting(metaclass=SingletonMeta):
    """A Singleton class to manage settings."""
    
    def __init__(self) -> None:
        self._props: Dict[str, Any] = {}

    def set(self, key: str, value: Any) -> None:
        """Set a key-value pair in the settings."""
        self._props[key] = value

    def get(self, key: str) -> Any:
        """Get the value associated with a key."""
        return self._props.get(key, None)

    def get_all(self) -> Dict[str, Any]:
        """Return all settings as a dictionary."""
        return self._props
Python

Demo

Use the code below, to create setting class instances.

# Demo usage
def main() -> None:
    # Create first setting instance
    setting = Setting()

    setting.set("file_base_path", "/var/log/dd")
    setting.set("app_port", 3000)

    print("First instance settings:")
    print(setting.get_all())

    # Try to create another instance
    setting2 = Setting()

    print("\nSecond instance settings (should be the same):")
    print(setting2.get_all())

    # Check if both instances are the same
    print("\nAre both instances the same?", setting is setting2)


if __name__ == "__main__":
    main()
Python

Output

Both instances have the same info, and both represent the same object.

First instance settings:
{'file_base_path': '/var/log/dd', 'app_port': 3000}

Second instance settings (should be the same):
{'file_base_path': '/var/log/dd', 'app_port': 3000}

Are both instances the same? 
True
Plaintext

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Singleton pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.