Question

How to Implement a Singleton in Python: Patterns, Tradeoffs, and Pythonic Alternatives

pythonsingletondecoratorbase-classmetaclass

Question

I have several classes that I might want to behave as singletons, for example a logger, although the exact use case is not the main point. I would prefer a solution that does not require repetitive boilerplate inside every class if inheritance or decoration can handle it.

I am comparing several possible approaches for implementing a singleton in Python and want to understand their tradeoffs.

Method 1: A decorator

def singleton(class_):
    instances = {}

    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]

    return getinstance


@singleton
class MyClass(BaseClass):
    pass

Pros

  • Decorators are additive and often feel more intuitive than multiple inheritance.

Cons

  • Objects created through MyClass() are singleton instances, but MyClass itself becomes a function rather than a class.
  • That means class methods and class-based introspection no longer behave normally.

For example:

x = MyClass()
y = MyClass()
t = type(x)

Here, x is y, but MyClass is no longer the original class object.

Method 2: A base class

class Singleton(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls)
        return cls._instance


class MyClass(Singleton, BaseClass):
    pass

Pros

  • MyClass remains a real class.

Cons

  • Requires multiple inheritance.
  • __new__ may become harder to reason about when combined with other base classes.

Method 3: A 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]


# Python 2
class MyClass(BaseClass):
    __metaclass__ = Singleton

# Python 3
class MyClass(BaseClass, metaclass=Singleton):
    pass

Pros

  • MyClass remains a real class.
  • The behavior naturally applies per class.
  • Uses the metaclass mechanism directly.

Cons

  • Are there any important downsides?

Method 4: A decorator that returns a subclass with the same name

def singleton(class_):
    class class_w(class_):
        _instance = None

        def __new__(cls, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, cls).__new__(cls)
                class_w._instance._sealed = False
            return class_w._instance

        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True

    class_w.__name__ = class_.__name__
    return class_w


@singleton
class MyClass(BaseClass):
    pass

Pros

  • MyClass remains a real class.
  • Inheritance is handled automatically.

Cons

  • It creates a wrapper subclass for every singleton class.
  • The _sealed attribute adds extra complexity.
  • It can interfere with super() and customization of __new__ or __init__.

Method 5: A module

Use a module file such as singleton.py and keep shared state there.

Pros

  • Very simple.

Cons

  • Not lazily instantiated in the same class-based way.

I am not looking for a debate about whether the singleton pattern is good or bad. Instead, I want to understand which implementation style is most Pythonic, especially in the sense of following the principle of least astonishment.

Short Answer

By the end of this page, you will understand what a singleton is in Python, how modules, __new__, decorators, and metaclasses can be used to implement it, and why Python developers often prefer simpler alternatives. You will also see the practical tradeoffs between a "true singleton class" and a module-level shared object.

Concept

A singleton is a pattern that ensures only one instance of something exists during a program's lifetime, while providing a global way to access it.

In Python, this idea appears often in things like:

  • application configuration
  • database connection managers
  • logging helpers
  • caches
  • service registries

The important Python-specific detail is this:

  • In many languages, singleton patterns are built into class design.
  • In Python, modules are already singletons in practice because a module is imported once and then reused.

That is why many Python developers ask a different question:

Do you really need a singleton class, or do you just need one shared object?

Why this matters

Choosing the wrong implementation can make code harder to understand.

For example:

  • A decorator that turns a class into a function can break class behavior.
  • A metaclass can work, but may be harder for beginners to read.
  • A module-level instance is simple and unsurprising.

In Python, the most Pythonic solution is usually the one that is:

  • easiest to read
  • easiest to test
  • least magical
  • least likely to surprise other developers

So the real lesson is not just how to build a singleton, but when a simpler shared-object design is better.

Mental Model

Think of a singleton like a single reception desk in an office.

  • Many people can ask for the desk.
  • But the office should not create a new reception desk each time.
  • Everyone is directed to the same one.

In Python, a module is like the building directory that already points everyone to the same desk.

A singleton class is more like adding a guard who checks:

  • "Has the desk already been created?"
  • If yes, return the existing one.
  • If not, create it once.

The simpler the guard system, the easier it is for everyone else in the office to understand what is happening.

Take Quiz

Syntax and Examples

1. Simplest Pythonic approach: module-level instance

# logger_service.py

class Logger:
    def log(self, message):
        print(f"LOG: {message}")


logger = Logger()

Use it like this:

from logger_service import logger

logger.log("Application started")

Why this is often preferred

  • The module is imported once.
  • logger is created once.
  • Other files reuse the same object.
  • It is easy to understand.

2. Singleton with __new__

class Logger:
    _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")

Usage:

a = Logger()
b = Logger()

print(a is b)  # True

Important note

__new__ controls object creation, but __init__ may still run every time you call Logger() unless you guard against repeated initialization.

Example with safe one-time initialization:

class Logger:
    _instance = None
    _initialized = False

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self.logs = []
        self._initialized = True

3. Singleton with a metaclass

class SingletonMeta(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=SingletonMeta):
    def __init__(self):
        self.logs = []

Usage:

a = Logger()
b = Logger()

print(a is b)  # True

This keeps Logger as a real class and is cleaner than turning the class into a function with a decorator.


4. Decorator approach that replaces the class

def singleton(cls):
    instance = None

    def wrapper(*args, **kwargs):
        nonlocal instance
        if instance is None:
            instance = cls(*args, **kwargs)
        return instance

    return wrapper

This works for returning one instance, but it changes the decorated class name into a callable wrapper rather than preserving normal class behavior. That makes it less suitable when you want normal class semantics.

Step by Step Execution

Consider this example using __new__:

class Config:
    _instance = None
    _initialized = False

    def __new__(cls):
        if cls._instance is None:
            print("Creating instance")
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if self._initialized:
            print("Already initialized")
            return
        print("Running initialization")
        self.settings = {"theme": "dark"}
        self._initialized = True


x = Config()
y = Config()

print(x is y)
print(x.settings)

What happens step by step

  1. x = Config() is executed.
  2. Python calls Config.__new__(Config).
  3. _instance is None, so a new object is created.
  4. Python then calls Config.__init__(x).
  5. _initialized is False, so initialization runs and settings is created.
  6. y = Config() is executed.
  7. Python calls Config.__new__(Config) again.
  8. _instance already exists, so the same object is returned.
  9. Python still calls Config.__init__(y).
  10. Because _initialized is now True, initialization is skipped.
  11. x is y prints True.
  12. x.settings prints {'theme': 'dark'}.

Expected output

Creating instance
Running initialization
Already initialized
True
{'theme': 'dark'}

Real World Use Cases

Shared configuration

A program may load configuration once and reuse it everywhere.

class AppConfig:
    _instance = None

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

Logging service

A single logger object can collect messages from different parts of the app.

Cache manager

A shared in-memory cache avoids creating separate caches in each module.

Database connection coordinator

Sometimes an app wants one connection manager object, even if the manager itself internally controls a pool.

Feature flag registry

A shared service can expose flags like is_beta_enabled across the application.

In practice, many of these are simpler as module-level objects rather than formal singleton classes.

Take Quiz

Real Codebase Usage

In real projects, developers often use one of these patterns instead of a heavy singleton implementation:

Module-level shared object

# config.py
settings = {"debug": True}

This is the most common and readable option when one shared object is enough.

Lazy module-level factory

_instance = None


def get_logger():
    global _instance
    if _instance is None:
        _instance = Logger()
    return _instance

This gives lazy initialization without metaclasses.

Dependency injection

Instead of global access, the object is created once and passed where needed.

class UserService:
    def __init__(self, logger):
        self.logger = logger

This is common in testable codebases because it avoids hidden global state.

Guarded initialization

If using __new__, developers commonly add an initialization guard.

Validation and error handling

Singleton-like services often validate startup configuration once, then raise a clear error if setup is invalid.

Why metaclasses are less common

Metaclasses can be correct, but many teams avoid them unless they truly need class-level customization, because they increase mental overhead for readers.

Common Mistakes

Mistake 1: Forgetting that __init__ can run more than once

Broken example:

class Logger:
    _instance = None

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

    def __init__(self):
        self.logs = []

Problem:

  • Every call to Logger() resets self.logs.

Fix:

class Logger:
    _instance = None
    _initialized = False

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

    def __init__(self):
        if self._initialized:
            return
        self.logs = []
        self._initialized = True

Mistake 2: Using == instead of is

a = Logger()
b = Logger()

print(a == b)

Problem:

  • == checks value equality.
  • is checks whether both names refer to the exact same object.

Use this:

print(a is b)

Mistake 3: Replacing the class with a function unintentionally

A decorator-based singleton may make this fail in surprising ways:

@singleton
class MyClass:
    pass

print(type(MyClass))

Now MyClass may be a function-like wrapper, not the original class.


Mistake 4: Making code hard to test

Global singletons can leak state between tests.

Avoid this by:

  • resetting shared state in tests
  • using dependency injection when possible
  • preferring simple factories for testable code

Mistake 5: Using a singleton where a module variable is enough

If all you need is one shared object, this is often simpler:

service = Service()

inside a module.

Comparisons

ApproachKeeps real class?Lazy creation?ComplexityCommon in Python?Notes
Module-level instanceYesNo, unless wrappedLowVery commonSimplest shared-object solution
Module-level factoryYesYesLowVery commonGood balance of simplicity and laziness
__new__ singletonYesYesMediumSometimesMust guard repeated __init__
Metaclass singletonYesYesHighLess commonPowerful, but more magical
Decorator returning wrapper functionNoYesMediumLess commonBreaks normal class behavior
Decorator returning subclassMostlyYesHighRareCan complicate inheritance and super()

__new__ vs metaclass

Feature__new__Metaclass
Beginner-friendlyMoreLess
Affects one class directlyYesYes
Reusable across many classesSomewhatYes
Easy to read in teamsUsuallySometimes not
Class semantics preservedYesYes

Most Pythonic choice

For most code:

  1. Module-level object if you just need one shared instance.
  2. Module-level factory if you want lazy creation.
  3. __new__ or metaclass only if you truly need singleton behavior at the class level.
Take Quiz

Cheat Sheet

Singleton quick reference in Python

Simplest shared object

# service.py
class Service:
    pass

service = Service()

Lazy shared object

_instance = None

def get_service():
    global _instance
    if _instance is None:
        _instance = Service()
    return _instance

__new__ singleton

class Service:
    _instance = None
    _initialized = False

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self._initialized = True

Metaclass singleton

class SingletonMeta(type):
    _instances = {}

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

Key rules

  • Use is, not ==, to verify singleton identity.
  • __new__ creates the object; __init__ initializes it.
  • __init__ may run multiple times unless guarded.
  • Modules already act like shared singletons in normal imports.
  • Prefer the least surprising design.

Edge cases

  • Singletons can make tests harder.
  • In multithreaded code, extra locking may be needed.
  • Decorators that replace the class can break introspection and class methods.

FAQ

Why are modules often considered singletons in Python?

Because Python imports a module once and then reuses the same module object, so module-level state is naturally shared.

Is using a metaclass the best way to implement a singleton in Python?

Not always. It is powerful, but often more complex than necessary. A module-level object or factory is usually simpler.

What is the difference between __new__ and __init__ in a singleton?

__new__ controls object creation. __init__ configures the object after creation. In a singleton, __init__ may be called multiple times unless you guard it.

How do I check whether two singleton variables are the same object?

Use is:

a is b

Are singletons bad in Python?

Not automatically, but they can introduce hidden global state and make testing harder. Use them only when shared state is truly needed.

What is the most Pythonic singleton alternative?

Usually a module-level instance or a module-level getter function.

Can a decorator-based singleton break my class?

Yes. If the decorator returns a function wrapper, the original class identity and behavior may be lost.

Should I use a singleton for logging in Python?

Often no. Python's built-in logging module already manages shared logger behavior well.

Take Quiz

Mini Project

Description

Build a small application-wide settings manager that behaves like a singleton. This project demonstrates how to ensure only one settings object exists while avoiding repeated initialization bugs.

Goal

Create a reusable Settings class where every call returns the same object and configuration is initialized only once.

Requirements

  • Create a Settings class using Python.
  • Ensure multiple Settings() calls return the same object.
  • Prevent the configuration data from being reset on repeated construction.
  • Add methods to set and get configuration values.
  • Show that two variables reference the same instance.
Take Quiz

Keep learning

Related questions

View more