Home Tutorials OOP

OOP

The Singleton Pattern in Python: Implementations and Why You Rarely Need One

Pyford Notes July 6, 2026 7 min read
Key points
  • 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.