Singleton Decorator Pattern
Ensures only one instance of a decorated class exists throughout application lifecycle.
Quick Reference
from neutrons_standard.decorators.singleton import Singleton, reset_Singletons
@Singleton
class MyService:
def __init__(self):
self.state = {}
# All references point to same instance
s1 = MyService()
s2 = MyService()
assert s1 is s2 # True
# Testing: reset before each test
MyService._reset_Singleton() # Reset one singleton
reset_Singletons() # Reset all singletons
reset_Singletons(fully_unwrap=True) # Remove decorator behavior
Basic Usage
@Singleton
class DatabaseConnection:
def __init__(self):
self.connection = self._connect()
def _connect(self):
return "Connected"
db1 = DatabaseConnection()
db2 = DatabaseConnection()
assert db1 is db2 # Same object
assert db1 is not DatabaseConnection() # Never creates new instances
Key Behaviors
Single Instance
Only one object created, all calls reuse it:
@Singleton
class Logger:
def __init__(self):
print("Initialized")
Logger() # Prints "Initialized"
Logger() # No print - reuses existing
Logger() # No print - reuses existing
Initialization Guard
__init__ runs only on first instantiation, skipped on subsequent calls:
@Singleton
class Counter:
def __init__(self):
self.count = 0
c1 = Counter()
c1.count = 5
c2 = Counter() # __init__ not called
c2.count # 5, not 0 - same instance
Reset and Testing
Reset individual singleton (allows re-initialization):
MyClass._reset_Singleton() # Next call creates new instance
MyClass._reset_Singleton(fully_unwrap=True) # Remove Singleton behavior
Reset all singletons:
from neutrons_standard.decorators.singleton import reset_Singletons
reset_Singletons() # Reset all
reset_Singletons(fully_unwrap=True) # Fully unwrap all
Examples
Pytest Fixture
import pytest
from neutrons_standard.decorators.singleton import reset_Singletons
@pytest.fixture(autouse=True)
def reset_all_singletons():
"""Fresh instance for each test."""
reset_Singletons()
yield
reset_Singletons()
def test_one():
obj1 = MyClass()
obj1.value = "test1"
def test_two():
obj2 = MyClass()
assert obj2.value != "test1" # Fresh instance
Selective Reset
@pytest.fixture
def fresh_database():
"""Fresh database instance."""
DatabaseConnection._reset_Singleton()
db = DatabaseConnection()
yield db
DatabaseConnection._reset_Singleton()
def test_with_fresh_db(fresh_database):
# fresh_database is guaranteed new instance
pass
Use Cases
Logging and Monitoring
@Singleton
class ApplicationLogger:
def __init__(self):
self.messages = []
def log(self, msg):
self.messages.append(msg)
# All modules log to same instance
logger = ApplicationLogger()
State Management
@Singleton
class SessionManager:
def __init__(self):
self.sessions = {}
self.current_user = None
def set_user(self, user):
self.current_user = user
# Application-wide state
mgr = SessionManager()
Configuration Objects
from neutrons_standard import Config # Already a Singleton
# Config is single instance across app
cfg = Config
cfg.reload()
host = cfg["database.host"]
Thread Safety
Not thread-safe by default. For multi-threaded use, add locking:
import threading
@Singleton
class ThreadSafeResource:
def __init__(self):
self.lock = threading.Lock()
self.data = []
def append(self, value):
with self.lock:
self.data.append(value)
Common Patterns
Lazy Initialization
@Singleton
class ExpensiveResource:
def __init__(self):
self.resource = None
def get_resource(self):
if self.resource is None:
self.resource = self._load()
return self.resource
Service Factory
@Singleton
class ServiceFactory:
def __init__(self):
self.services = {}
def register(self, name, service):
self.services[name] = service
def get(self, name):
return self.services.get(name)
Pitfalls
State Pollution Between Tests
Always reset singletons in test fixtures to prevent state leakage between tests.
Over-Use
Too many singletons make code harder to test and reason about. Use when truly needed for application-wide shared resources.
Mutable Class Variables
# Bad - shared mutable
@Singleton
class BadClass:
items = [] # Shared across all
# Good - instance variable
@Singleton
class GoodClass:
def __init__(self):
self.items = []