Python Singleton Pattern Implementation Approaches
The Singleton Pattern ensures that a class has only one instance throughout the application lifecycle. This pattern proves useful when exactly one object is needed to coordinate actions across the system.
Module-Based Singleton
Python modules functon as natural singletons. When a module is first imported, Python generates a .pyc file. Subsequent imports load the cached bytecode instead of re-executing the module code. By encapsulating functions and data within a single module, you effectively create a singleton object.
# config.py
class Settings:
def get_value(self, key):
return f"value_{key}"
config = Settings()
from config import config
Decorator-Based Singleton
A decorator maintains a dictionary mapping classes to their instances. Upon first invocation, the decorator creates and stores the instance. Subsqeuent calls retrieve the cached instance instead of creating a new one.
def singleton(cls):
_registry = {}
def get_instance(*args, **kwargs):
if cls not in _registry:
_registry[cls] = cls(*args, **kwargs)
return _registry[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self, host="localhost"):
self.host = host
print(f"Initializing connection to {host}")
conn1 = DatabaseConnection("192.168.1.1")
conn2 = DatabaseConnection("192.168.1.2")
print(id(conn1) == id(conn2)) # True
Output:
Initializing connection to 192.168.1.1
True
__new__ Method Singleton
The __new__ method controls instance creation, while __init__ handles initialization. By overriding __new__, you can intercept the instantiation process and return an existing instance if available.
import time
class AppState:
_instance = None
def __init__(self):
self.timestamp = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def update_timestamp(self):
self.timestamp = time.time()
if __name__ == "__main__":
state1 = AppState()
state2 = AppState()
print(f"Same instance: {id(state1) == id(state2)}")
state1.update_timestamp()
print(f"State1 timestamp: {state1.timestamp}")
print(f"State2 timestamp: {state2.timestamp}")
Output:
Same instance: True
State1 timestamp: 1593963540.9315283
State2 timestamp: 1593963540.9315283
Metaclass-Based Singleton
Metaclasses provide another elegant soluiton by controlling class creation itself.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
def __init__(self):
self.level = "INFO"
def log(self, message):
print(f"[{self.level}] {message}")
logger1 = Logger()
logger2 = Logger()
print(id(logger1) == id(logger2)) # True
Each approach offers distinct advantages: modules require no code changes, decorators provide flexibility, __new__ offers simplicity, and metaclasses deliver elegant reuse across multiple classes.