Implementing a Python Singleton with Decorators
This article explains the singleton design pattern in Python, demonstrates how to implement it using a decorator with a wrapper class, provides step‑by‑step code examples, discusses its advantages, appropriate use cases, and cautions against overuse.
In Python, the singleton pattern ensures a class has only one instance and provides a global point of access, which is useful for managing shared resources such as database connections or logging systems.
This article walks through implementing a singleton using a decorator, explaining the underlying concepts and showing the code step by step.
What is a Decorator?
A decorator in Python is a function that modifies or extends the behavior of another function or class without changing its source code.
Singleton Pattern Explanation
The singleton pattern guarantees that a class has only one instance; subsequent requests return the existing instance instead of creating a new one.
Code Breakdown
1. The wrapper class _SingletonWrapper stores the original class and holds the singleton instance.
class _SingletonWrapper:
"""A singleton wrapper class. Its instances would be created
for each decorated class."""
def __init__(self, cls):
self.__wrapped__ = cls
self._instance = None2. The special method __call__ makes the wrapper callable and creates the instance on first use.
def __call__(self, *args, **kwargs):
"""Returns a single instance of decorated class"""
if self._instance is None:
self._instance = self.__wrapped__(*args, **kwargs)
return self._instance3. The decorator function singleton returns a wrapper object for the target class.
def singleton(cls):
"""A singleton decorator. Returns a wrapper object. A call on that object
returns a single instance object of decorated class. Use the __wrapped__
attribute to access the decorated class directly in unit tests."""
return _SingletonWrapper(cls)Using the Singleton Decorator
Example of applying the decorator to a Logger class and verifying that multiple instantiations refer to the same object.
@singleton
class Logger:
def __init__(self):
self.log = []
def write_log(self, message):
self.log.append(message)
def read_log(self):
return self.log
logger1 = Logger()
logger2 = Logger()
logger1.write_log("Log message 1")
print(logger2.read_log()) # Output: ['Log message 1']
print(logger1 is logger2) # Output: TrueAdvantages of Using a Singleton
Global Access: Centralized control of a shared resource such as a database connection or logging service.
Efficiency: Reusing the same instance reduces memory usage and speeds up access, especially for resource‑intensive objects.
Ease of Testing: The underlying class can be accessed via the wrapper’s __wrapped__ attribute, allowing unit tests to bypass the singleton behavior.
When to Avoid Singletons
Overusing singletons can lead to tight coupling, make code harder to test and maintain, and introduce global state that may cause issues in multithreaded environments or when scaling the application.
Conclusion
If applied appropriately, the singleton pattern is a powerful tool. Using the decorator approach shown here lets you enforce singleton behavior while keeping the code clean and reusable; however, it is important to understand its trade‑offs and use it judiciously.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.