The Singleton Pattern in Python: Why It's Usually an Anti-Pattern
Share this article
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.
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.