A Singleton pattern in Python is a design pattern that allows you to create just one instance of a class, throughout the lifetime of a program.
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(DatabaseConnection, cls).__new__(cls)
# Initialize the instance (e.g., establish the database connection)
cls._instance.connection = cls._connect_to_database()
return cls._instance
@staticmethod
def _connect_to_database():
return "Database Connection Established"
# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1.connection) # Output: Database Connection Established
print(db1 is db2) # Output: True (both references point to the same instance)
- To limit concurrent access to a shared resource.
- To create a global point of access for a resource.
- To create just one instance of a class, throughout the lifetime of a program.
A singleton pattern can be implemented in three different ways. They are as follows:
- Module-level Singleton
- Classic Singleton
- Borg Singleton
All modules are singleton, by definition.
Let’s create a simple module-level singleton where the data is shared among other modules. Here we will create three python files – example_module_singleton.py, sample_module1.py, and sample_module2.py – in which the other sample modules share a variable from singleton.py.
# singleton.py
shared_variable = "Shared Variable"
# sample_module1.py
import example_singleton as singleton
print(singleton.shared_variable)
singleton.shared_variable += "(modified by samplemodule1)"
# sample_module2.py
import example_singleton as singleton
print(singleton.shared_variable)
Now run the both scripts in same Python session. Example to do so.
# singleton_main.py
import sample_module1
import sample_module2
Now run the command below.
python3 singleton_main.py
Shared Variable
Shared Variable(modified by samplemodule1)
The first line is from sample_module1.py
, and the second line is from sample_module2.py
after modifying the variable shared_variable
in sample_module1.py
.
Here, the value changed by sample_module1 is also reflected in sample_module2.
Classic Singleton creates an instance only if there is no instance created so far; otherwise, it will return the instance that is already created.
class SingletonClass(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(SingletonClass, cls).__new__(cls)
return cls.instance
singleton = SingletonClass()
new_singleton = SingletonClass()
print(singleton is new_singleton)
singleton.singl_variable = "Singleton Variable"
print(new_singleton.singl_variable)
True
Singleton Variable
Here, in the __new__
method, we will check whether an instance is created or not. If created, it will return the instance; otherwise, it will create a new instance. You can notice that singleton and new_singleton return the same instance and have the same variable.
Now, subclass a singleton class.
class SingletonClass(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(SingletonClass, cls).__new__(cls)
return cls.instance
class SingletonChild(SingletonClass):
pass
singleton = SingletonClass()
child = SingletonChild()
print(child is singleton)
singleton.singl_variable = "Singleton Variable"
print(child.singl_variable)
True
Singleton Variable
SingletonChild
has the same instance of SingletonClass
and also shares the same state. But there are scenarios, where we need a different instance, but should share the same state. This state sharing can be achieved using Borg Singleton
.
Borg singleton is a design pattern in Python that allows state sharing for different instances.
class BorgSingleton(object):
_shared_borg_state = {}
def __new__(cls, *args, **kwargs):
obj = super(BorgSingleton, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_borg_state
return obj
borg = BorgSingleton()
borg.shared_variable = "Shared Variable"
class ChildBorg(BorgSingleton):
pass
childBorg = ChildBorg()
print(childBorg is borg)
print(childBorg.shared_variable)
False
Shared Variable
The new instance creation process, a shared state is also defined in the __new__
method. Here the shared state is retained using the shared_borg_state attribute and it is stored in the __dict__
dictionary of each instance.
To achieve a different state, then reset the shared_borg_state
attribute.
class BorgSingleton(object):
_shared_borg_state = {}
def __new__(cls, *args, **kwargs):
obj = super(BorgSingleton, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_borg_state
return obj
borg = BorgSingleton()
borg.shared_variable = "Shared Variable"
class NewChildBorg(BorgSingleton):
_shared_borg_state = {}
newChildBorg = NewChildBorg()
print(newChildBorg.shared_variable)
Here the shared state is reseted and while try to access the shared_variable an error will occure.
Traceback (most recent call last):
File "/home/329d68500c5916767fbaf351710ebb13.py", line 16, in <module>
print(newChildBorg.shared_variable)
AttributeError: 'NewChildBorg' object has no attribute 'shared_variable'