top of page

Demystifying the Singleton Pattern in Python: Proper Usage, and Its Controversial status

In the world of Python design patterns, the Singleton pattern stands as a unique and sometimes controversial approach to handling class instances. In this post, we will explore the Singleton pattern, covering both eager and lazy initialization methods, their pros and cons, and where they are commonly used, like in reporter classes. I will also discuss why this pattern is often deemed an anti-pattern by some, and present code examples to illustrate both implementations.


So what is the Singleton Pattern?

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. In other words, no matter how many times we request an object of a Singleton class, we will always get the same instance.


1. Eager Initialization - ABC Metaclass Example:

Eager initialization creates the instance of the Singleton class at the time of class definition, regardless of whether it is used or not. One way to implement eager initialization is by using the Abstract Base Classes (ABC) metaclass.


from abc import ABCMeta

class Singleton(metaclass=ABCMeta):
    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

class Reporter(Singleton):
    def __init__(self):
        self.log = []

    def add_log(self, message):
        self.log.append(message)

# Usage
reporter1 = Reporter.get_instance()
reporter2 = Reporter.get_instance()

print(reporter1 is reporter2)  # Output: True


The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance

Pros of Eager Initialization:

- Thread-safe: Eager initialization guarantees that the instance is created before any threads can access it, ensuring thread safety.

- Simple to implement: The ABC metaclass simplifies the implementation of eager initialization.


Cons of Eager Initialization:

- Memory usage: Eager initialization consumes memory even if the Singleton instance is not used.


2. Lazy Initialization:

Lazy initialization creates the instance of the Singleton class only when it is first requested, improving memory efficiency by creating the instance on-demand.



class SingletonLazy:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

class ConfigManager(SingletonLazy):
    def __init__(self):
        self.config_data = {}

    def set_config(self, key, value):
        self.config_data[key] = value

    def get_config(self, key):
        return self.config_data.get(key)

# Usage
config_manager1 = ConfigManager()
config_manager2 = ConfigManager()

print(config_manager1 is config_manager2)  # Output: True


Pros of Lazy Initialization:

- Memory-efficient: Lazy initialization creates the instance only when needed, saving memory when the Singleton instance is not used.

- Customization: The `__new__` method allows customization of the Singleton instance creation process.


Cons of Lazy Initialization:

- Not thread-safe by default: Lazy initialization is not inherently thread-safe, and additional synchronization mechanisms may be required to ensure thread safety.


Where is the Singleton Pattern Commonly Used?

The Singleton pattern is commonly used in scenarios where having multiple instances of a class can lead to conflicts or unnecessary resource consumption. Some common use cases include:

- Configuration managers

- Logger or reporter classes

- Database connection handlers


An example to make you understand better:


Imagine you append to a HTML file each time test is finished data about the test - status, steps, path to screenshots.

This Reporter class and it's methods has to first, on start create an HTML file with header and some styling. Then append to it. And then wrap the html up.


Without singleton - it will cause some clunky implementation since file creation can't happen in Reporter class constructor. You will have to instantiate Reporter object in setup - use method to start the file, then in each test class instantiate again the object and use method that appends to it. And in teardown again instantiwte the object, get the html file and close it so it will be usable.


Pretty clunky, eh? Do you see any problems that might occur? I see few. For example file is not properly closed and wrote operation fails.


However, with singleton you can instantiate a reporter object once - in constructor invoke method that builds the "top" of html document. Then simply refer the object and use it's method to write output. And in "__del__" method of the class to have the html wrap up itself.

Additionally Reporter logic is confined to only one class and with this approach I limit point of access to bare minimum avoiding errors.

Simple, efficient and elegant.


Why is the Singleton Pattern Considered an Anti-Pattern by Some?

While the Singleton pattern offers a solution to certain design challenges, it is often criticized for several reasons:

- Global state: Singleton introduces global state, making it harder to manage and test dependencies.

- Tight coupling: Code using the Singleton pattern becomes tightly coupled to the Singleton instance, hindering flexibility and testability.

- Difficult to extend: Extending Singleton classes can be challenging, leading to code that is hard to maintain.


The Singleton pattern in Python, with both eager and lazy initialization methods, serves as a powerful design pattern for scenarios requiring a single instance of a class.


However, it is essential to use it judiciously, considering the trade-offs between memory efficiency and thread safety. By understanding the pros and cons of the Singleton pattern, you can make informed decisions on when and where to apply it in your Python projects.

Comments


Subscribe to QABites newsletter

Thanks for submitting!

  • Twitter
  • Facebook
  • Linkedin

© 2023 by QaBites. Powered and secured by Wix

bottom of page