Please Stop Asking About Singleton in Python

Summary

The Singleton pattern is frequently misapplied in Python, with common implementations introducing issues such as type changes, broken inheritance, or repeated `__init__` calls. Approaches using decorators, class variables, or even metaclasses often fail to fully prevent unexpected state changes when arguments are passed to subsequent instantiations. Instead, a simple module-level variable is the most effective and Pythonic way to achieve a singleton, exposing a pre-instantiated object rather than a class. This method naturally prevents re-instantiation and argument passing, aligning with the pattern's intent while maintaining clarity. For scenarios requiring `isinstance` checks, the class can be designed to explicitly disallow further instantiation after the initial creation.

One can’t help but sigh at how the Singleton pattern, as a classic design pattern, has been abused—especially in Python. It has even become a formulaic interview question, asked over and over again without end. But I dare say, in the vast majority of cases, people answer by rote, and even the answers they reference online are rarely correct.

First, the conclusion

  • In Python, you don’t need a Singleton.
  • If you do need one, use a module-level variable.

As for why, let’s look at several popular Singleton implementations:

1. 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

# Usage
@singleton
class MyClass:
    ...

Unless otherwise specified, the code is provided by Copilot.

The problem with this approach is obvious: applying the decorator changes the type of the object—from a class into a function. If someone tries to use isinstance(obj, MyClass) to check the object type, it will throw an error.

This involves another issue: how your code will be used depends on what you expose. In the example above, what’s exposed is the MyClass object itself, so you need to consider whether someone might try to use it as a class and whether that is a reasonable expectation.

2. Class variable

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

The problem with this approach is that if there are multiple singleton types, you have to write multiple such classes, and this class also cannot be inherited—if inherited, they would actually still share the same _instance variable, causing conflicts, which is not what we want. It’s the same old problem: if you expose a class, others will use it as a class.

The second problem is that this approach does not block the __init__ call. In fact, if you instantiate the Singleton class multiple times, although the returned object id is the same, the __init__ method will still be called multiple times, which might not be what you want.

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

    def __init__(self):
        print('init called')

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)

# Output
# init called
# init called
# True

3. Inheritance

class Singleton:
    _instances = {}

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

# Usage
class MyClass(Singleton):
    ...

class MyAnotherClass(Singleton):
    ...

What’s exposed here is actually a base class, whose standard use is for you to inherit from. It solves the first problem of the previous approach, but the second problem still remains.

4. Metaclass

class Singleton(type):
    _instances = {}

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

# Usage
class MyClass(metaclass=Singleton):
    ...

This approach uses a metaclass—the ultimate solution that comes to mind for those aiming to master Python’s advanced language features. It intercepts the instantiation call at the metaclass level, not just the __new__ method, because instantiation includes both __new__ and __init__. This solves the second problem mentioned earlier. If you run the test code again, you’ll find the output meets expectations:

s1 = MyClass()
s2 = MyClass()
print(s1 is s2)

# Output
# init called
# True

Perfect, isn’t it? But don’t get too pleased—look at the following code:

class MyClass(metaclass=Singleton):
    def __init__(self, name: str) -> None:
        self.name = name

s1 = MyClass('Alice')
s2 = MyClass('Bob')
print(s1.name, s2.name)
# Output
# Alice Alice

Do you think this output matches the author’s intent? You can’t say yes or no—it’s debatable and depends on how the caller views the singleton pattern. One consideration is that if you use approaches 2 and 3, the value of the name attribute will be uniformly changed to “Bob”, which already reflects a problem. You might say no one passes arguments when instantiating a singleton class—I’m not sure about that—but I think if someone does it, it’s because your exposed interface allows it.

5. Module-level variable

class Singleton:
    ...

singleton = Singleton()

# Usage
from singleton_module import singleton

Plain and simple, no flashy tricks. How can using this answer show that I’m proficient in Python? On the contrary, I think this approach best meets the original requirement, with relatively few problems and easiest to understand. Just like the three stages of life, in the end you have to strip away the glamour and return to the essence of the requirement. In this approach, the exposed object is the unique singleton module variable—you can’t use it as a class, and you can’t pass arguments to it. This is exactly what we want. You can even name the class _Singleton to discourage people’s ideas (of course, this isn’t a hard ban—if they want to, they still can, but let’s not quibble over that).

Now, what if I still want to expose the Singleton class for things like isinstance checks? I admit that need exists, so let’s slightly modify it:

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is not None:
            raise TypeError("Singleton class cannot be instantiated twice")

        cls._instance = super().__new__(cls)
        return cls._instance

singleton = Singleton()

It looks similar to approach 2, but the main exposed object has now shifted from the class to the instance. This class no longer allows instantiation. This is controlling the exposed interface at the code level.

The article is translated from https://frostming.com/2025/singleton/ and original author is Frost Ming

PYTHON DRAWBACK SINGLETON GUIDE

  RELATED

  COMMENTS

0

No comment for this article.