A single object can only be instantiated for a class thanks to the singleton design pattern. The Singleton pattern may be implemented in Python in some methods, each having pros and cons of its own. We’ll go over the many approaches to creating Singletons in Python, weigh the benefits and drawbacks of each, and give examples of each way in this in-depth tutorial.
A class is guaranteed to have a single instance according to the Singleton pattern, which also offers a global point of access to that instance. It is frequently used in situations (such as handling database connections, logging, and configuration settings) where just one instance of a class is needed for the duration of an application.
Using a Class Variable
Using a class variable to store one instance of the class is one of the easiest approaches to building Singleton in Python. Here’s an example:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Unique Through Decorator
PEP 318 — Decorator for Functions and Methods — introduced the decorator syntax to Python in 2003. In Python, a decorator is a function or method extension. It can therefore add functionality without changing the original expanded code at all.
All I have to do to construct one is create a function within a function that yields the single instance of a class. Next, use our Target class’s @singleton syntax decorator.
def singleton(class_):
instances = {}
def get_instance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return get_instance
@singleton
class Database:
def __init__(self, url):
self.url = url
def connect(self):
if 'https' not in self.url:
raise ValueError("invalid url: it must be encrypted")
return True
Singleton Via Metaclass
A unique method for automatically changing a class during creation is provided by Metaclass. It can define the behavior expected of that class.
To prevent creating an object of the Target class if one already exists, I can write a metaclass. The derived class building will be orchestrated by __call__, and __init__will only be called once.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(type(cls), cls).__call__(*args, **kwargs)
return cls._instances[cls]
class _Database():
def __init__(self, url):
self.url = url
def connect(self):
if 'https' not in self.url:
raise ValueError("invalid url: it must be encrypted")
return True
class DatabaseSingleton(_Database, metaclass=Singleton):
pass
Using a Module
Since Python modules are only imported once per interpreter session, they are by default singletons. By putting the singleton logic in a module and importing it anywhere we want access to the singleton instance, we may benefit from this behavior.
# singleton.py
class Singleton:
pass
singleton_instance = Singleton()
Using a Borg (Shared State) Pattern
Multiple instances of the same class can share the same state thanks to the Borg pattern, also called the Shared State pattern. Even if it doesn’t follow the Singleton pattern exactly, sharing state among instances allows for comparable outcomes.
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class Singleton(Borg):
pass
Comparing the Approaches
Every Python Singleton implementation technique has benefits and drawbacks of its own. Several criteria, including ease of use, speed, and compatibility with the current codebase, influence the technique selection.
Method | Pros | Cons |
Class Variable | Easy and uncomplicated execution | Not thread-safe |
Decorator | isolates the class definition from the singleton logic | Requires modifying class definition |
Metaclasses | Strong and adaptable personalization | Complex syntax |
Module | Unintentional singleton generation | restricted to singletons without states |
Borg Pattern | allows for the shared state between instances | requires the Borg class to be inherited. |
This article has several approaches to creating Singleton in Python, including utilizing decorators, metaclasses, class variables, modules, and the Borg pattern. Every approach has advantages and disadvantages of its own, and the best approach will rely on the particular needs of the application.
Consider learning more about the Singleton pattern and its uses in software design for more investigation. Moreover, investigate additional Python implementations of design patterns to deepen your grasp of software architecture and design concepts.