The Singleton Pattern in Python: Why It's Usually an Anti-Pattern
#Python

The Singleton Pattern in Python: Why It's Usually an Anti-Pattern

LavX Team
2 min read

While implementing a singleton class offers valuable lessons about Python's object creation mechanics, it's rarely necessary in practice. Discover Pythonic alternatives leveraging modules and composition that provide cleaner, more maintainable solutions for shared resources.

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 Image 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.

Comments

Loading comments...