When developers encounter scenarios requiring a single shared instance—like a leaderboard in a game or a configuration manager—the singleton pattern often seems appealing. But as Stephen Gruppetta explains in The Python Coding Stack, implementing a true singleton in Python involves complex workarounds and introduces subtle pitfalls. Here's why you should reconsider this classic design pattern.

The Singleton Temptation and Its Costs

A singleton class restricts instantiation to a single object. Gruppetta demonstrates this with a Leaderboard class where multiple game instances must share scores. The initial implementation fails because creating new Leaderboard objects resets the state:

class Leaderboard:
    def __init__(self):
        self.scores = {}  # Resets on re-initialization

To enforce singleton behavior, developers override __new__:

class Leaderboard:
    _instance = None

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

This ensures only one instance exists, but introduces another problem: __init__ reapplies on subsequent calls, wiping existing data. The fix requires guarding against re-initialization:

def __init__(self):
    if not hasattr(self, "initialised"):
        self.scores = {}
        self.initialised = True

Now you have:
1. Thread-safety risks: Concurrent calls might bypass checks.
2. Testability challenges: Hard to isolate or replace the singleton.
3. Hidden complexity: Non-standard initialization confuses users.

Article illustration 3

Complex initialization logic needed for thread-unsafe singletons

Pythonic Alternatives

1. Module-Level Singleton

Python modules are singletons by design. Define your class and instantiate once:

# leaderboard.py
class Leaderboard:
    def __init__(self):
        self.scores = {}

leaderboard = Leaderboard()  # Single instance

Import anywhere:

from leaderboard import leaderboard
leaderboard.add_score("Alice", 10)

2. Functional Approach

For simpler cases, avoid classes entirely:

# leaderboard.py
_scores = {}

def add_score(player, points):
    _scores[player] = _scores.get(player, 0) + points

3. Service Container (Best for Flexibility)

Encapsulate shared resources in a container class:

# services.py
from leaderboard import Leaderboard

class GameServices:
    def __init__(self):
        self.leaderboard = Leaderboard()

game_services = GameServices()

Benefits:
- Swappable dependencies: Substitute Leaderboard with a mock during testing.
- Extensible: Add new services (logging, config) without global variables.
- Explicit ownership: Clearer than magic singleton methods.

When Would You Actually Need a Singleton?

Almost never. As Gruppetta notes:

"Creating a singleton class is a useful exercise to help you understand how Python creates and initializes objects. From a learning perspective, it’s great. But in your code? Generally, no."

The exceptions are rare—like interfacing with C libraries requiring singleton access. For 99% of cases, Python’s module system or service containers offer cleaner, more maintainable solutions.

Key Takeaways

  • Singletons complicate: They require non-standard Python mechanics.
  • Modules are natural singletons: Leverage Python’s import system for shared state.
  • Composition beats inheritance: Service containers promote flexibility and testability.
  • Question the pattern: If you think you need a singleton, first ask if a module or functions suffice.

Source: Adapted from Creating a Singleton Class in Python And Why You (Probably) Don’t Need It by Stephen Gruppetta. Code visuals created with Snappify.