Home » Python Singleton Class Tutorial

Python Singleton Class Tutorial

A Singleton is a design pattern that restricts the instantiation of a class to a single instance. In other words, no matter how many times you try to create an object of the singleton class, you will always get the same instance.

The Singleton pattern is useful when you need to ensure that a class has only one instance, like in scenarios where you need a single database connection, configuration handler, or logging service.

In Python, there are several ways to implement the Singleton pattern. This tutorial will explore the common techniques with practical examples.

1. Using a Class Attribute

One of the simplest ways to implement a Singleton in Python is to use a class attribute to store the single instance. Every time you create an object, you check if the instance already exists; if it does, return it.

Example: Singleton Using a Class Attribute

# Example: Singleton using class attribute
class Singleton:
    _instance = None  # Class attribute to store the singleton instance

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

# Testing the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

_instance is a class attribute that holds the single instance of the class.
The __new__() method is overridden to check if an instance already exists. If it doesn’t, a new instance is created; otherwise, the existing instance is returned.

2. Using a Decorator

You can create a decorator to enforce the Singleton pattern. A decorator can be applied to any class, and it ensures that the class only has one instance.

Example: Singleton Using a Decorator

# Example: Singleton using a decorator
def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Singleton:
    def __init__(self):
        print("Instance created")

# Testing the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

The singleton decorator wraps the class and ensures that only one instance is created by maintaining a dictionary of instances (instances).

3. Using a Metaclass

A more Pythonic way to implement a Singleton is through a metaclass. A metaclass controls the creation of classes themselves, so you can use a metaclass to manage the creation of Singleton instances.

Example: Singleton Using a Metaclass

# Example: Singleton using a metaclass
class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(metaclass=SingletonMeta):
    def __init__(self):
        print("Instance created")

# Testing the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

The SingletonMeta metaclass overrides the __call__() method, which controls the creation of instances. If an instance already exists, it is returned; otherwise, a new one is created.

4. Using the __new__() Method

Another common approach is to override the __new__() method in the class. The __new__() method is responsible for creating a new instance of the class, so by controlling this, we can enforce the Singleton pattern.

Example: Singleton Using __new__()

# Example: Singleton using __new__()
class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("Creating new instance")
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

# Testing the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

The __new__() method ensures that only one instance of the class is created. The first time the class is instantiated, a new instance is created; after that, the same instance is returned.

5. Using the abc Module (Abstract Base Classes)

You can also implement a Singleton pattern using abstract base classes (ABCs) in Python. This is less common but useful when dealing with inheritance and ensuring that subclasses still follow the Singleton pattern.

Example: Singleton Using abc Module

# Example: Singleton using abc module
from abc import ABC, abstractmethod

class SingletonABC(ABC):
    _instance = None

    @abstractmethod
    def do_something(self):
        pass

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(SingletonABC, cls).__new__(cls)
        return cls._instance

class MySingleton(SingletonABC):
    def do_something(self):
        print("Doing something")

# Testing the Singleton
singleton1 = MySingleton()
singleton2 = MySingleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

SingletonABC is an abstract base class that implements the Singleton pattern. The MySingleton class inherits from it and uses the same Singleton behavior.

6. Thread-Safe Singleton

In multi-threaded environments, it is important to ensure that the Singleton implementation is thread-safe. You can achieve this by using locks.

Example: Thread-Safe Singleton

# Example: Thread-safe Singleton using locks
import threading

class Singleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                print("Creating new instance")
                cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

# Testing the Singleton
singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

A lock (_lock) is used to ensure that only one thread can create an instance of the Singleton class at a time, preventing race conditions.

7. A Global Singleton Class

Another approach is to use a global instance that is accessible across the entire program, ensuring that the Singleton class is used globally.

Example: Global Singleton

# Example: Global Singleton
class Singleton:
    _instance = None

    def __init__(self):
        print("Instance created")

def get_singleton_instance():
    if Singleton._instance is None:
        Singleton._instance = Singleton()
    return Singleton._instance

# Testing the Singleton
singleton1 = get_singleton_instance()
singleton2 = get_singleton_instance()

print(singleton1 is singleton2)  # Output: True (Both are the same instance)

In this example:

The get_singleton_instance() function ensures that there is only one global instance of the Singleton class.

Summary

The Singleton pattern ensures that a class has only one instance, which is useful for managing shared resources like database connections or logging.
The class attribute method is one of the simplest ways to implement a Singleton, using the __new__() method or a class attribute to store the instance.
You can also use a decorator to wrap the class and enforce the Singleton pattern.
A metaclass provides a more Pythonic way to control the creation of class instances and can be used to implement Singleton behavior.
In multi-threaded applications, it’s important to implement a thread-safe Singleton using locks.
Using the abc module ensures that even classes that inherit from an abstract base class follow the Singleton pattern.

By mastering these techniques, you can implement Singleton classes in Python and ensure proper management of resources in your applications.

You may also like

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More