Contents
Chapter 24

Singleton

A singleton is the simplest design pattern: a class with exactly one instance. Before using a classic implementation, ask whether the language already provides a solution. For the singleton, Python does.

A Module Is Already a Singleton

Python imports each module once and caches it in sys.modules. Every import after the first produces the same module object. A module is a singleton, and anything defined at module level is shared, with one copy for the whole program. Put the state in a module:

# config.py
settings: dict[str, str] = {}

Then every import of settings, from anywhere, hands back the same dict. Mutating it through one import is visible through every other:

# shared_config.py
from config import settings

settings["theme"] = "dark"
print(settings)
#: {'theme': 'dark'}

No class, no ceremony. For the majority of singleton needs, the module approach solves the problem.

Mutation is what makes the sharing work. Rebinding is the mistake that quietly ends it. from config import settings gives your module its own name for the same dict object named by config.settings. Mutating through your name, settings["theme"] = "dark", changes that shared object, so every module sees it. But settings = {} in your module rebinds only your module’s name, and the two modules silently diverge: config.settings still holds the old dict, while your code now talks to a private one. To replace the whole value, go through the module: import config, then config.settings = {...}. Mutate through any name. Rebind only through the module.

When You Want a Class, Cache the Instance

Sometimes you do want a class, but every construction should return the same object. The simplest solution is to hide construction behind a cached factory. This applies functools.cache to a constructor function, which is an ordinary function whose only job is to build and return an instance of a class. It stands in for a direct call to the class constructor.

functools.cache memoizes a function. The first call with a given set of arguments runs the function and stores the result. Every repeat call with those arguments returns the stored result. A constructor function with no arguments has only one possible call, so caching it constructs the instance once and returns that same object forever:

# singleton_cached_factory.py
from dataclasses import dataclass, field
from functools import cache

@dataclass
class Settings:
    data: dict[str, str] = field(default_factory=dict)

@cache
def settings() -> Settings:
    return Settings()

a = settings()
b = settings()
assert a is b
a.data["theme"] = "dark"
print(b)
#: Settings(data={'theme': 'dark'})

You can’t prevent a caller from writing Settings() and getting a second instance. Naming the class _Settings marks it internal and keeps it out of from module import *, which is the extent that Python offers. A second underscore is not a stronger version of that. At module level nothing is mangled, so it buys no privacy, and it breaks any reference written inside a class body, which rewrites m.__Settings into a lookup for _TheClass__Settings.

This listing keeps the bare name for a reason that outlasts the convention. settings() returns a Settings, so the class already appears in the module’s public signature. A caller who annotates the result must write that name, and a type outsiders must name is not private, whatever it starts with. Only if the type never left the module would we use _Settings.

Two stronger-looking moves fail the same way. Deleting the name after building the instance leaves the class reachable, because type(settings()) hands it back. Defining the class inside settings() also looks airtight, and @cache runs that body only once, so there is exactly one instance and no module-level name. type(settings()) recovers it anyway.

Nesting costs the return annotation as well. Quoting the name, def settings() -> "Settings", is the obvious approach. It parses and runs because an annotation is evaluated only when something reads it. A clean run is therefore no evidence, since nothing has looked the name up yet. When something does look, it searches the scope containing the function, not the function’s own locals, and the class is not there. A checker reports an unresolved reference, and inspect.get_annotations(eval_str=True) raises a NameError. The signature must name something reachable, which means a Protocol.

Privacy in Python is advice, not enforcement. An underscore asks callers to stay out, and nothing makes them. Rethinking Objects makes the same case about hidden data, where a getter hands back a reference to the internals it was meant to protect. It also turns out that the reachable class is useful when a test needs a fresh, uncached Settings.

Three implementation notes:

  1. A singleton is shared state, and shared state leaks between tests. The cached factory has an escape hatch the classic forms lack: settings.cache_clear() discards the instance, so each test can start fresh.

  2. Every lazy singleton has a first-call race under threads. Concurrent first calls can each run the constructor, and each caller can end up holding a different object, with only one of them staying in the cache. This is not a narrow window. Eight threads calling settings() at once, with a constructor slow enough to widen it, ran that constructor eight times and handed back eight different objects. When threads can arrive before the singleton exists, create it eagerly instead: call settings() once at import time, or use the module form, which the import system builds exactly once.

  3. A lock is the other fix for that race, and not the obvious one. Wrapping the cached function’s body in a threading.Lock changes nothing, because every thread has already missed the cache before reaching the lock. They serialize, each still builds an object, and the cache keeps whichever finished last. The check must happen inside the lock, shown in the listing below.

@cache is gone, because it is no longer what makes the object single:

# singleton_locked_settings.py
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from typing import Final

@dataclass
class Settings:
    data: dict[str, str] = field(default_factory=dict)

    def __post_init__(self) -> None:
        time.sleep(0.05)  # Widen the first-call window

_lock: Final[threading.Lock] = threading.Lock()
_instance: Settings | None = None

def settings() -> Settings:
    global _instance
    with _lock:
        if _instance is None:
            _instance = Settings()
    return _instance

with ThreadPoolExecutor(max_workers=8) as pool:
    built = list(pool.map(lambda _: settings(), range(8)))
print(len({id(s) for s in built}))
#: 1

In settings(), only one of the two module-level names is declared global. This is the chapter’s opening distinction seen from inside a function. global governs rebinding, not use. with _lock: only looks the name up, even though acquiring and releasing genuinely changes that lock’s state, from unlocked to locked and back. Changing an object is not rebinding a name. _instance differs because the function assigns to it. Python decides at compile time that a name a function assigns anywhere is local everywhere in that function, so without the global declaration, if _instance is None would read an unassigned local and raise UnboundLocalError. Mutate through any name. Declare only what you rebind.

One thread finds _instance empty and builds it. The rest wait on the lock, and each finds _instance already filled. The eight-thread race that produced eight objects from the cached version produces one here, which the printed count confirms. The sleep stands in for a constructor that does real work, such as opening a file or a connection. Without it, the cached version showed no duplicates across twenty trials, which is the more dangerous case. A window too narrow to reproduce is still a window.

Every call now acquires the lock, including the thousands that arrive long after the object exists. That is the price of laziness under threads. Eager creation is a better answer when the object can be built at import time.

If you need the class to hand back one instance from its own constructor, override __new__(), shown below.

Modules and cached factories should cover your singleton needs. The rest of this chapter exists only because it demonstrates interesting techniques and insights.

The Classic Implementations

To address languages like C++ and Java, GoF Design Patterns builds the singleton with more apparatus. The variations shown here are worth understanding, but notice that each does more work than the module or the cached factory above.

The classic approach takes control of creation by delegating to a single instance of a private nested class.

Lazy Creation

This version builds the inner instance on the first call. It is lazy. It builds the inner object on the first call, which is why it needs the None sentinel and the if guard.

# singleton_pattern.py
from dataclasses import dataclass, field
from typing import Any, ClassVar

class OnlyOne:
    @dataclass
    class __OnlyOne:
        val: list[str] = field(default_factory=list)

    instance: ClassVar[__OnlyOne | None] = None

    def __init__(self, arg: str) -> None:
        if OnlyOne.instance is None:
            OnlyOne.instance = OnlyOne.__OnlyOne()
        OnlyOne.instance.val.append(arg)

    def __getattr__(self, name: str) -> Any:
        return getattr(self.instance, name)

x = OnlyOne("sausage")
print(x.val)
#: ['sausage']
y = OnlyOne("eggs")
print(y.val)
#: ['sausage', 'eggs']
z = OnlyOne("spam")
print(z.val)
#: ['sausage', 'eggs', 'spam']
# Distinct wrappers (x is not y), one shared inner instance:
print(x is y, x.instance is y.instance is z.instance)
#: False True

Because the inner class’s name starts with a double underscore, Python’s compiler rewrites it to _OnlyOne__OnlyOne wherever it appears inside OnlyOne’s body. This is name mangling. OnlyOne.__OnlyOne, written from outside the class, names an attribute that was never stored under that spelling, so it fails at runtime with AttributeError, not at type-checking time. The outer class controls creation through its constructor. The first time you create an OnlyOne it initializes instance. After that it reuses the one inner object, and each construction appends its argument to that object’s shared list. __getattr__() delegates access. The distinct OnlyOne instances all proxy to the same __OnlyOne object.

Eager Creation

When the object needs nothing from that first call, you can create the inner instance eagerly in the class body instead, which removes the sentinel and the guard:

# singleton_eager.py
from dataclasses import dataclass, field
from typing import Any, ClassVar

class OnlyOne:
    @dataclass
    class __OnlyOne:
        val: list[str] = field(default_factory=list)

    # Created once, when the class is defined:
    instance: ClassVar[__OnlyOne] = __OnlyOne()

    def __init__(self, arg: str) -> None:
        OnlyOne.instance.val.append(arg)

    def __getattr__(self, name: str) -> Any:
        return getattr(self.instance, name)

x = OnlyOne("sausage")
y = OnlyOne("eggs")
# Distinct wrappers (x is not y), one shared inner list:
print(x.val, x is y, x.instance is y.instance)
#: ['sausage', 'eggs'] False True

The bare __OnlyOne() works because the nested class is already defined at that point in the body. The qualified OnlyOne.__OnlyOne() would fail, because the name OnlyOne stays unbound until its own class body finishes running.

__getattr__() returns Any in both versions, and unlike instance, that one cannot be tightened away. It answers for every name Python fails to find on the wrapper, so its return type is whatever the inner object happens to hold under that name. instance is a single declared field and can say __OnlyOne. __getattr__() covers an open set of names and cannot. Delegation trades static knowledge for reach, which is the cost Surrogate pays throughout.

The two forms differ only in when they create the inner object. The lazy form defers it to the first OnlyOne(...) call, so it can wait for data not available at import time, but it carries the sentinel and the guard, and two threads racing on that first call can both see None. The eager form creates the object once, at module import: no sentinel, no guard, and no race, at the cost of building it whether or not anything uses it.

Either way, this is a lot of code for what a module does on its own.

Overriding __new__

We can use __new__(), the method that creates an instance, to return the same object every time:

# singleton_with_new.py
from dataclasses import dataclass, field
from typing import Any, ClassVar

class OnlyOne:
    @dataclass
    class __OnlyOne:
        val: list[str] = field(default_factory=list)

    instance: ClassVar[__OnlyOne | None] = None

    def __new__(cls) -> Any:  # Implicitly a staticmethod
        if OnlyOne.instance is None:
            OnlyOne.instance = OnlyOne.__OnlyOne()
        return OnlyOne.instance

x = OnlyOne()
x.val.append("sausage")
y = OnlyOne()
y.val.append("eggs")
z = OnlyOne()
z.val.append("spam")
# __new__ returns the one instance every time, so all three share val:
print(x.val, x is y is z)
#: ['sausage', 'eggs', 'spam'] True
assert OnlyOne() is OnlyOne()

Because __new__() returns the inner __OnlyOne object, that is what OnlyOne() hands back, so x is the shared instance itself, not a wrapper around it. This is why no __getattr__() appears here, while the two versions above need one. Those return an OnlyOne wrapper that has no val of its own, so the lookup must be forwarded to the inner object. Here x.val is an ordinary attribute access on the object that owns it. Note that x is y is False for the wrappers, and x is y is z is True when the inner object is produced.

Python calls __init__() only when __new__() returns an instance of the class being constructed. Here it returns something else, the inner object, so no __init__() ever runs. That has a second consequence: x is not an OnlyOne, so isinstance(x, OnlyOne) is False. The metaclass version at the end of this chapter returns the class’s own instance, which puts it on the other side of the rule.

One Instance in a Class Variable

The nested private class is not required. Here we keep the single instance in a class variable. __new__ builds it, when needed, from the class being constructed:

# singleton_class_variable.py
from typing import ClassVar

class SingletonClassVar:
    val: list[str]
    __instance: ClassVar[SingletonClassVar | None] = None

    def __new__(cls, arg: str) -> SingletonClassVar:
        if SingletonClassVar.__instance is None:
            SingletonClassVar.__instance = object.__new__(cls)
            SingletonClassVar.__instance.val = []
        SingletonClassVar.__instance.val.append(arg)
        return SingletonClassVar.__instance

if __name__ == "__main__":
    x = SingletonClassVar("sausage")
    y = SingletonClassVar("eggs")
    z = SingletonClassVar("spam")
    print(x.val, x is y is z)
#: ['sausage', 'eggs', 'spam'] True

object.__new__(cls) builds a SingletonClassVar, not a foreign object, which puts this version on the other side of the rule from singleton_with_new.py. There, __new__() handed back the inner class, so __init__() never ran and isinstance(x, OnlyOne) was False. Here isinstance(x, SingletonClassVar) is True, and Python would call __init__() on the shared instance after every construction if the class defined one. SingletonClassVar defines none, so all the work happens in __new__().

Borg: Singleton By Inheritance

Alex Martelli observes that what you usually want is not one object but one shared set of state. People can create as many objects as they like, as long as they all share the same data. He called this the Borg.1 It points every instance’s __dict__ at the same storage:

x, y, and z are three distinct objects, but every dict points at the same _shared_state, so the last write wins for all three

In contrast with the previous singleton designs, you reuse Borg through inheritance:

# singleton_borg.py
from typing import Any, ClassVar

class Borg:
    _shared_state: ClassVar[dict[str, Any]] = {}

    def __init__(self) -> None:
        self.__dict__ = self._shared_state

class Singleton(Borg):
    def __init__(self, arg: str) -> None:
        super().__init__()
        self.val = arg

    def __str__(self) -> str:
        return self.val

x = Singleton("sausage")
y = Singleton("eggs")
z = Singleton("spam")
# Last write wins on the shared state; distinct objects, one __dict__:
print(x.val, x is y, x.__dict__ is y.__dict__ is z.__dict__)
#: spam False True

Unlike the nested-class examples above, Singleton should not be a @dataclass. Although that still runs, it quietly stops being a Borg. The shared state depends on super().__init__ rebinding self.__dict__ to _shared_state. A dataclass generates its own __init__ that assigns the fields and never calls the base __init__, so self.__dict__ is never rebound and each instance keeps its own state. Moving the rebinding into __post_init__ does not help either. That runs after __init__ assigns the fields, so it discards them. The hand-written __init__ is what makes the shared state work, and silently losing the sharing is worse than failing outright.

Testing confirms the objects differ but share one set of state:

# test_singleton_borg.py
from singleton_borg import Singleton

def test_borg_shares_state_but_not_identity() -> None:
    x = Singleton("first")
    y = Singleton("second")
    assert x is not y  # Distinct objects
    assert x.val == y.val  # But sharing one set of state
    assert x.val == "second"

Singleton Classes

You can use a class decorator to wrap a class so that calling it returns a cached instance:

# singleton_class.py
from typing import Any

class singleton:
    def __init__(self, constructor: type) -> None:
        self.constructor = constructor
        self.instance: Any = None

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        print(f"singleton.__call__({args}, {kwargs})")
        if self.instance is None:
            print(f"constructing {self.constructor.__name__}")
            self.instance = self.constructor(*args, **kwargs)
        else:
            print(f"using cached {self.constructor.__name__}")
            print(f"discarding {args}, {kwargs}")
        return self.instance

@singleton
class Registry:
    def __init__(self, name: str, *, limit: int = 10) -> None:
        print(f"Registry.__init__({name}, {limit})")
        self.name = name
        self.limit = limit
        self.items: list[str] = []

first = Registry("primary", limit=3)
#: singleton.__call__(('primary',), {'limit': 3})
#: constructing Registry
#: Registry.__init__(primary, 3)
first.items.append("spam")
first.items.append("eggs")
second = Registry("secondary", limit=99)
#: singleton.__call__(('secondary',), {'limit': 99})
#: using cached Registry
#: discarding ('secondary',), {'limit': 99}
print(first is second, second.name, second.limit, second.items)
#: True primary 3 ['spam', 'eggs']

You might wonder why the constructor for a Registry is intercepted by __call__(). A call is parentheses written after an expression. That expression can produce anything: a function, a class, or an instance. To evaluate obj(...), Python looks up __call__() on the type of obj. The result depends on that type. For an ordinary class, type(Plain) is type, and the parentheses run type.__call__(), the machinery that invokes __new__() and then __init__(). After decoration, type(Registry) is singleton, so the same parentheses run singleton.__call__() instead, and the wrapped class is constructed only when that method decides to call it. __call__() forwards *args and **kwargs to the constructor of the wrapped class, so Registry("primary", limit=3) reaches the real constructor unchanged.

Only the first constructor call produces the construction of a Registry object. Every later constructor call returns the cached instance and discards the constructor arguments, which is why Registry("secondary", limit=99) does not create a new object. A caller who believes those arguments took effect is holding an object configured by someone else.

Applying @singleton to Registry runs Registry = singleton(Registry). The name Registry now refers to the decorated instance rather than to the class. Calling Registry(...) returns the cached instance.

Both isinstance(first, Registry) and subclassing Registry raise exceptions:

# test_singleton_class.py
import pytest
from singleton_class import Registry

def test_isinstance_rejects_the_decorated_name() -> None:
    with pytest.raises(TypeError, match="arg 2 must be a type"):
        isinstance(Registry("primary"), Registry)  # type: ignore

def test_subclassing_the_decorated_name_fails() -> None:
    with pytest.raises(TypeError, match="takes 2 positional"):
        class Sub(Registry):  # type: ignore
            pass

The type checker complains that defining Sub raises a TypeError at runtime. singleton.__init__() takes two positional arguments and receives four, because a class statement passes the name, bases, and namespace to whatever it inherits from. Nothing in class Sub(Registry) mentions singleton, so the error names a class that does not appear in the failing line. That is the confusion a class decorator costs you. The __new__() versions above and the metaclass version below keep the name pointing at a real class, which is the reason to prefer those.

Singleton Using Metaclasses

Finally, a metaclass can intercept construction. Metaprogramming shows another metaclass singleton, one that overrides __call__(). That chapter also covers __init_subclass__() and __set_name__(), the simpler hooks that replace most metaclasses. This version is here for completeness:

# singleton_metaclass.py
from collections.abc import Callable
from typing import Any

class SingletonMetaClass(type):
    def __init__(cls, name: str, bases: tuple[type, ...],
                 namespace: dict[str, Any]) -> None:
        super().__init__(name, bases, namespace)
        klass: Any = cls
        original_new: Callable[..., Any] = klass.__new__

        def my_new(c: Any, *args: Any, **kwargs: Any) -> Any:
            if c.instance is None:
                c.instance = original_new(c)
            return c.instance

        klass.instance = None
        klass.__new__ = staticmethod(my_new)

class Bar(metaclass=SingletonMetaClass):
    def __init__(self, val: str) -> None:
        self.val = val

    def __str__(self) -> str:
        return self.val

x = Bar("sausage")
y = Bar("eggs")
z = Bar("spam")
# Each Bar(...) reruns __init__ on the one instance, so val is spam:
print(x, x is y is z)
#: spam True

cls is the class being created, and the metaclass modifies it in ways no annotation describes. It attaches an instance attribute that does not exist yet, and it replaces __new__(). klass: Any = cls is the escape hatch that lets those assignments past the type checker. Annotating klass as type fails. That resolves klass.__new__ to type.__new__, the constructor that builds classes, when the line actually captures Bar.__new__, which is object.__new__.

This is the other side of the __new__() rule. my_new() returns an instance of Bar itself, so Python calls __init__() after every construction, on the same shared object. Each Bar(...) call therefore overwrites val, which is why x prints as spam. The Overriding __new__ version returned a foreign object, so its __init__() never ran. Same pattern, opposite __init__() behavior, and the difference is only what __new__() returns.

Which Should You Use?

Use the lightest tool that fits:

The elaborate GoF Design Patterns singleton is largely a workaround for languages where a module is not a first-class, single-instance namespace. In Python, most of the ceremony falls away.

Exercises

  1. singleton_eager.py always creates its inner object, even if nothing ever uses it. Modify it to use lazy initialization, then compare your result with singleton_pattern.py.
  2. Using singleton_cached_factory.py as a starting point, create a factory that manages a fixed pool of objects (say, database connections) and hands them out, rather than a single instance.
  3. Rewrite one of the class-based singletons above as a module, and argue which you would use in real code.
  4. In shared_config.py, replace the mutation with a rebinding, settings = {"theme": "dark"}, and add import config plus print(config.settings) at the end. Predict both printed values before running it, and explain the difference using the binding-versus-mutation distinction from A Module Is Already a Singleton.