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.
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)
PythonOutput:
some val: abc
some val: abc
True
Method #2: Using Global Object Pattern
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)
PythonOutput:
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
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}")
PythonDemo
# 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()
PythonOutput
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
PlaintextExample #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
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]
PythonSingleton Class
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
PythonDemo
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()
PythonOutput
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
PlaintextSource Code
Use the following link to get the source code:
Example | Python Implementation Source Code Link |
---|---|
Example #1: Database Connection | GitHub |
Example #2: Setting | GitHub |
Other Code Implementations
Use the following links to check Singleton pattern implementation in other programming languages.