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
No comment for this article.