How to Create a Singleton in Python (and Why You Probably Shouldn’t)

by SkillAiNest

Singleton pattern Ensures that a class has exactly one instance throughout your application. You’ve probably seen it in configuration managers, database connections, or logging systems. Although singletons seem useful, they often cause more problems than they solve.

In this tutorial, I show you how to implement singletons in Python, explain when they might be appropriate, and discuss better alternatives for most use cases.

You can find the code On GitHub.

Conditions

Before we begin, make sure you have:

  • Python 3.10 or higher Install

  • Understanding of Python classes and decorators

  • Familiarity with object oriented programming concepts

No external libraries are required as we will only use Python’s standard library.

Table of Contents

  1. What is a singleton?

  2. The classic singleton pattern

  3. Decorator pattern

  4. The metaclass approach

  5. Thread-safe singleton

  6. Why you probably shouldn’t use singletons

  7. Better alternatives to the singleton pattern

  8. When singletons are acceptable

What is a singleton?

A singleton is a design pattern that restricts a class to a single instance. No matter how many times you try to create an object from that class, you always get the same instance.

A classic use case is a configuration object. You want all parts of your application to share the same configuration, not create separate copies. Instead of passing a Config object everywhere, the singleton pattern lets you access it globally.

Here’s the problem: the global state is problematic. When any part of your code can modify shared state, debugging becomes difficult. You lose the ability to reason about code in isolation. Tests become difficult because they share state between runs.

Despite these issues, there are some real use cases. Let’s explore how to create singletons correctly, then discuss when you really need them.

The classic singleton pattern

The traditional approach uses a class variable to store a single instance. When you try to create a new instance, the class checks if one already exists.

class DatabaseConnection:
    """
    Classic Singleton pattern using __new__
    """
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("Creating new database connection")
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        
        if not self._initialized:
            print("Initializing database connection")
            self.connection_string = "postgresql://localhost/mydb"
            self.pool_size = 10
            self._initialized = True

    def query(self, sql):
        return f"Executing: {sql}"

Let’s now examine the singleton behavior:

db1 = DatabaseConnection()
print(f"db1 connection: {db1.connection_string}")

print("\nCreating second instance:")
db2 = DatabaseConnection()
print(f"db2 connection: {db2.connection_string}")

print(f"\nAre they the same object? {db1 is db2}")

Output:

Creating new database connection
Initializing database connection
db1 connection: postgresql://localhost/mydb

Creating second instance:
Are they the same object? True

__new__ Procedures control the creation of objects in Python. By looking at this, we stop instance creation and return our stored instance if it exists. __init__ The procedure still runs each time, so we add one _initialized Flag to prevent restart.

This pattern works, but it’s verbose and easy to mess up. _initialized The flag feels like a hack. Let’s look at the cleaner approach.

Decorator pattern

A more Python approach to handling singleton logic uses a decorator. This keeps the class clean and redirects singleton behavior to reusable decorators.

def singleton(cls):
    """
    Decorator that converts a class into a singleton
    """
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances(cls) = cls(*args, **kwargs)
        return instances(cls)

    return get_instance

@singleton
class AppConfig:
    """
    Application configuration as a singleton
    """
    def __init__(self):
        print("Loading configuration...")
        self.debug_mode = True
        self.api_key = "secret-key-12345"
        self.max_connections = 100
        self.timeout = 30

    def update_setting(self, key, value):
        setattr(self, key, value)
        print(f"Updated {key} = {value}")

As with the previous approach, let’s examine the decorator approach:


config1 = AppConfig()
print(f"Debug mode: {config1.debug_mode}")


print("\nAccessing config again:")
config2 = AppConfig()
config2.update_setting("timeout", 60)

print(f"\nconfig1 timeout: {config1.timeout}")
print(f"Same instance? {config1 is config2}")

Output:

Loading configuration...
Debug mode: True

Accessing config again:
Updated timeout = 60

config1 timeout: 60
Same instance? True

The decorator pattern is clear. @singleton Wraps the decorator class and maintains the instances in the closure. This keeps the singleton logic separate from the class implementation. The class itself is simple and testable.

Consider how to edit config2 is affected config1 As they are the same thing. This shared state can be useful but also dangerous. Any code that receives the config can modify it, potentially breaking other parts of your application.

For more control, you can use metaclasses. Metaclasses control class creation themselves, making them a natural fit for singletons.

class SingletonMeta(type):
    """
    Metaclass that creates singleton instances
    """
    _instances = {}

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

class Logger(metaclass=SingletonMeta):
    """
    Simple logging singleton using metaclass
    """
    def __init__(self):
        self.logs = ()

    def log(self, message):
        self.logs.append(message)
        print(f"(LOG) {message}")

    def get_logs(self):
        return self.logs

Let’s examine the above metaclass approach to singleton construction:


logger1 = Logger()
logger1.log("Application started")
logger1.log("User logged in")


logger2 = Logger()
logger2.log("Processing request")

print(f"\nTotal logs in logger1: {len(logger1.get_logs())}")
print(f"Total logs in logger2: {len(logger2.get_logs())}")
print(f"Same logger? {logger1 is logger2}")

Output:

(LOG) Application started
(LOG) User logged in
(LOG) Processing request

Total logs in logger1: 3
Total logs in logger2: 3
Same logger? True

If you’re comfortable with metaclasses, the metaclass approach is beautiful. __call__ The method intercepts class instantiation, allowing us to return the current instance. It happens at a much deeper level than that __new__making it stronger.

However, metaclasses add complexity. Most Python developers don’t work with them regularly, which makes code difficult to understand. Use this approach only if you need to provide additional control metaclasses.

Thread-safe singleton

Previous implementations are not thread-safe. In multithreaded applications, two threads can create an instance simultaneously. Let’s fix it.

import threading

class ThreadSafeSingleton:
    """
    Thread-safe singleton using a lock
    """
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                
                if cls._instance is None:
                    print(f"Thread {threading.current_thread().name}: Creating instance")
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if not self._initialized:
            with self._lock:
                if not self._initialized:
                    print(f"Thread {threading.current_thread().name}: Initializing")
                    self.data = {}
                    self._initialized = True

Now let’s test the above singleton with multiple threads and verify that it is a singleton that contains only one instance:


def create_singleton(thread_id):
    instance = ThreadSafeSingleton()
    instance.data(thread_id) = f"Data from thread {thread_id}"

threads = ()
for i in range(5):
    t = threading.Thread(target=create_singleton, args=(i,), name=f"Thread-{i}")
    threads.append
    t.start()

for t in threads:
    t.join()


final = ThreadSafeSingleton()
print(f"\nShared data across all threads: {final.data}")

Output:

Thread Thread-0: Creating instance
Thread Thread-0: Initializing

Shared data across all threads: 
{0: 'Data from thread 0', 
1: 'Data from thread 1', 
2: 'Data from thread 2', 
3: 'Data from thread 3', 
4: 'Data from thread 4'}

A lock ensures that only one thread creates an instance. The double-check pattern avoids acquiring a lock on every access. We only install locks when there can be none for example. This is more efficient than locking every time.

Note on Python 3.13+: Python 3.13 introduced a Create an option for free-threaded modeand it has become even more Mainstream in Python 3.14. With true concurrency, thread safety becomes even more important. Global Spoke Lock (GIL) It previously masked some race conditions by preventing true parallel execution. In free-threaded Python, such explicit locking becomes necessary for accuracy, not good practice. If you are writing code for Python 3.13+ with free-threading enabled, use appropriate synchronization primitives such as locs for shared mutable state.

Why you probably shouldn’t use singletons

Now that you know how to create a singleton, let me explain why you shouldn’t do it too often.

Singletons are world states in disguise. Global state makes code difficult to understand, test, and maintain. When code can access and modify a singleton, you lose the ability to reason about your code natively. Changes to one module can break another through shared state.

Singletons make testing difficult. Tests should be independent, but singletons carry state between tests. You need to reset the singleton before each test, which is error prone. Worse, you can’t easily mock singletons for testing.

Singletons violate this Principle of Sole Liability. The class handles both its own basic logic and the singleton mechanism. This mix of concerns makes code difficult to maintain.

Python has better alternatives. Module-level objects are natural singletons. Dependency injection provides better control. Context managers cleanly manage the lifetime of resources.

Better alternatives to the singleton pattern

Instead of singletons, consider these patterns.

A module-level example

A module-level example Python has natural singletons. Import a module, and you get the same instance every time. Here’s how you can do it:


class Config:
    def __init__(self):
        self.debug = True
        self.api_key = "secret-key"

    def update(self, key, value):
        setattr(self, key, value)


config = Config()

It’s simpler and more pythonic. The module system ensures that you get the same example. No special patterns are required. You can use it like this:


from config import config

config.update("debug", False)
print(f"Debug mode: {config.debug}")

Let’s now take a closer look at how and why it works. Python’s module system itself is a singleton mechanism: when you import a module, Python processes it and returns its result. sys.modules. Each subsequent import returns the cached module object, not the new one.

when config.py The first time it runs, it creates Config instance and assigns it to a module-level variable config. During the initial import, this happens only once. Any other file that imports config This module gets a reference to the same object, not a new instance. So from config import config It will always give you the same thing in multiple files Config For example, achieving singleton behavior without special patterns, metaclasses, or decorations.

Dependency injection

Dependency injection Gives you control without global state. This solves the singleton problem by making dependencies explicit parameters instead of hidden global state. Here is an example:

class DatabaseConnection:
    def __init__(self, connection_string):
        self.connection_string = connection_string

    def query(self, sql):
        return f"Executing {sql}"

class UserRepository:
    def __init__(self, db):
        self.db = db

    def get_user(self, user_id):
        return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")

Instead UserRepository Internally creating or accessing a global database singleton, it obtains a database connection through its constructor (__init__) means you control exactly which database instance is used. In production you go through a real DatabaseConnectionbut in tests you can pass a mock object that doesn’t actually connect to a database.

Here is the key UserRepository It doesn’t know or care whether it’s getting a singleton, a mock, or a fresh instance each time. It just knows that it has found something that has one query method


db = DatabaseConnection("postgresql://localhost/mydb")
user_repo = UserRepository(db)

result = user_repo.get_user(123)

This makes code dependencies visible in the function signature, eliminates hidden global state, trivializes checking (only passing different objects), and gives you full control over object lifecycles without the need for any singleton patterns.

When singletons are acceptable

Despite the drawbacks, some cases justify the use of singletons. Some of them are:

Hardware interface which represent unique physical resources. You can have a camera, a printer, or a GPIO interface. A singleton models this correctly.

Caching layers Where you want the same shared cache across your application. Even here though, dependency injection can be neat.

Thread pool or connection pool Where you want to limit and share expensive resources. The pool itself can be a singleton, although its resources are not managed.

Even in these cases, ask yourself: Can I use dependency injection instead? Can I make it a module level instance? The answer is often yes.

The result

I hope you find this tutorial helpful. You’ve learned several ways to implement singletons in Python: the classic pattern, decorations, metaclasses, and thread-safe variations. Each approach has tradeoffs in complexity, readability, and thread safety.

More importantly, you learned why singletons are often not the best solution. Global state, difficulty checking, and violating design principles make singletons problematic. Module-level instance and dependency injection generally provide a better alternative.

When you get to singletons, pause and ask: Do I really need a shared global state? Often the answer is no. But when you need it, now you know how to implement it properly.

If not at all, use singletons sparingly.

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro