A Testable Singleton Decorator
The `singleton-decorator` is a Python library (version 1.0.0, last released in 2017) that provides a simple decorator to implement the singleton design pattern for classes. It aims to address common pitfalls of other singleton implementations, specifically making the decorated classes more amenable to unit testing by exposing the original class via a `__wrapped__` attribute. The library is stable but not actively developed.
Warnings
- gotcha The `__init__` method of a decorated singleton class is only executed during the *first* instantiation. Subsequent calls to the class constructor (e.g., `MyClass()`) will return the existing singleton instance, but any arguments passed in these later calls will be completely ignored and will not affect the already initialized instance.
- gotcha While `singleton-decorator` is designed to mitigate issues with `isinstance()` checks and direct static method calls by providing `__wrapped__`, many simpler singleton decorator implementations can break these functionalities. This library's explicit use of `__wrapped__` allows access to the original class definition.
- gotcha The `singleton-decorator` does not explicitly implement thread-safety mechanisms. In a multi-threaded application, it is possible for multiple threads to concurrently attempt to create the first instance, potentially leading to the creation of more than one singleton object, thus violating the pattern.
- gotcha The singleton pattern itself is often considered an anti-pattern due to its potential to introduce global state, tight coupling between components, and challenges in unit testing. While `singleton-decorator` attempts to make testing easier, these inherent drawbacks of the pattern should be carefully considered.
Install
-
pip install singleton-decorator
Imports
- singleton
from singleton_decorator import singleton
Quickstart
from singleton_decorator import singleton
@singleton
class MyClass:
def __init__(self, value=0):
self.value = value
print(f"MyClass initialized with value: {self.value}")
def get_value(self):
return self.value
# First instance creates and initializes
obj1 = MyClass(10)
print(f"Obj1 value: {obj1.get_value()}")
# Second instance returns the same object, __init__ is NOT called again
obj2 = MyClass(20) # Arguments are ignored after the first call
print(f"Obj2 value: {obj2.get_value()}")
print(f"Are obj1 and obj2 the same instance? {obj1 is obj2}")
# Accessing the original class for testing or static methods
# Note: Use __wrapped__ for direct class method calls or inspection in tests
print(f"Accessing original class via __wrapped__: {MyClass.__wrapped__.__name__}")