The Singleton Pattern in Python: Implementations and Why You Rarely Need One
- A singleton guarantees exactly one instance of a class exists for the lifetime of the program, with every call to construct it returning the same object.
- Overriding
__new__to return a cached instance is the classic OOP implementation, but it has to guard against re-running__init__on every subsequent call, which happens automatically in Python whether or not__new__returned a new object. - A Python module is already a singleton in practice — it's imported once and cached in
sys.modules, so module-level state or a module-level instance often achieves the same guarantee with far less code. - The classic singleton conflicts with testability: hidden global state makes tests harder to isolate, and dependency injection usually solves the same underlying problem more flexibly.
What a singleton actually guarantees
The singleton pattern promises that a class can only ever have one instance, and that every attempt to obtain "the" instance — from anywhere in the program — returns that same object rather than constructing a new one. It's reached for when a single shared point of coordination genuinely makes sense: a configuration object loaded once, a connection pool manager, a logging registry that every part of an application needs to agree is the same object.
The pattern originates from languages where there's no natural module-level scope to hold shared state, so a class with enforced single-instance semantics was the available workaround. That context matters, because Python's import system already provides that scope directly.
Implementing it: overriding __new__
The textbook implementation overrides __new__ to check for and return an already-created instance instead of building a fresh one:
class ConfigManager:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, path="config.json"):
self.path = path
self.data = load_config(path)
There's a subtle trap here worth knowing about: Python always calls __init__ after __new__ returns, regardless of whether __new__ built a genuinely new object or handed back the cached one. So ConfigManager() called a second time re-runs __init__ and reloads the config file from disk again, even though the object identity is unchanged — the guard needs to live in __init__ too (checking a flag like self._initialized) if re-running setup logic on every call isn't wanted, which it usually isn't.
Modules are already singletons
Python caches every imported module in sys.modules keyed by name, so a module is only ever executed once no matter how many places import it — every import config anywhere in the program gets a reference to the exact same module object:
# config.py
_data = load_config("config.json")
def get(key, default=None):
return _data.get(key, default)
Every other file does import config; config.get("db_host") and shares the same underlying _data. This achieves the identical guarantee — one shared instance, loaded once — with no class, no __new__ override, and no risk of the re-initialization trap above. It reads as ordinary Python rather than a pattern borrowed from a language without module-level state.
The thread-safety problem lazy singletons have
The __new__-based implementation above has a race condition under multiple threads: two threads can both pass the if cls._instance is None check before either has finished constructing the object, resulting in two instances briefly existing and one silently discarding the other's initialization work. A correct thread-safe version needs an explicit lock around the check-and-create step:
import threading
class ConfigManager:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
This is easy to forget in code copied from a single-threaded example, and the bug only shows up under real concurrent load, making it a particularly unpleasant one to track down later.
Why a plain module-level instance is often better
Beyond the module-as-singleton option above, dependency injection — passing the shared object explicitly to whatever needs it, rather than having code reach out and grab "the" global instance from inside a class method — tends to age better as a codebase grows. A hidden singleton makes unit tests harder to isolate, since tests that mutate shared global state can bleed into each other unless careful cleanup runs between them, and it hides a dependency that would otherwise be visible in a function's parameters. None of this means the pattern is wrong to reach for; it means that in Python specifically, the classic OOP implementation is usually solving a problem the language's own module system, or a passed-in instance, already solves more simply.