Implement a singleton in Python

Compare module-level instances, metaclass caching, and __new__ in Python, including repeated initialization, thread safety, and testing trade-offs.

Best practices
Python programming and application logging

For one shared object inside a Python application, a module-level instance is often sufficient. If construction must be controlled through a class, a metaclass can cache an instance per class. Neither approach creates a single object shared across separate processes.

Cache an instance with a metaclass

class Singleton(type):
    _instances = {}

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

class Logger(metaclass=Singleton):
    pass

print(Logger() is Logger())  # True

The metaclass intercepts ordinary construction. The first call constructs and initializes an object; later calls return it without calling its initializer again. Later constructor arguments are therefore ignored. This minimal example is not thread-safe: concurrent calls can race between the check and creation. It also does not define special behavior for copying, pickling, or unusual construction paths.

Cache through new

class Logger:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

This example suits a simple class without a subclassing contract. Python can still call __init__ on every normal construction, even when __new__ returns an existing instance. Guard initialization if it has side effects, or choose another design. Inheritance also needs care: a subclass can inherit the parent's cached _instance.

Use a module-level object

# app_logger.py
class Logger:
    pass

logger = Logger()

Other modules can use from app_logger import logger. Ordinary imports reuse a module cached under its import name. Reloading a module, loading it under another name, or running another process can create another instance, so avoid describing this as an unconditional global guarantee.

FAQ

How do I handle threads? A lock can protect the cache lookup and creation, but think about reentrant construction and initialization dependencies as well. Creating one shared object during startup and passing it to its consumers is often simpler. Sharing the instance does not make its methods thread-safe.

What are the design costs? Shared mutable state can hide dependencies and leak between tests. Dependency injection gives tests explicit control over instance lifetime and replacements.

Can I use a decorator? Yes. A wrapper can cache the result of constructing a class. A simple function decorator replaces the class name with a function, however, which can complicate isinstance, introspection, and subclassing. It is not interchangeable with preserving a normal class interface.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting