Singleton is a bad idea

Sunday 10 April 2022

Design patterns are a great way to think about interactions among classes. But the classic Singleton pattern is bad: you shouldn’t use it and there are better options.

The classic Singleton pattern is a class which always gives you the same object when you create an instance of the class. It’s used to ensure that all users of a class are using the same object.

The first problem with Singleton is that it encourages you to mix together two different ideas into one class. The first idea is whatever your class is about. Let’s say you are writing a chess game. Your ChessBoard class should only be concerned with chess-board-ness. A separate second idea is that your program only needs one board. That’s not a fact intrinsic to chess boards, so it shouldn’t be coded into your ChessBoard class.

If your program only needs one of a certain object, you can just make one:

class ChessBoard:
    def __init__(self):
        ...

the_chess_board = ChessBoard()

If you want centralized management of the instance, use a function to manage a global:

_the_chess_board = None

def the_chess_board():
    global _the_chess_board
    if _the_chess_board is None:
        _the_chess_board = ChessBoard()
    return _the_chess_board

Or let functools do it for you:

@functools.cache        # new in 3.9
def the_chess_board():
    return ChessBoard()

If you still really want the class to manage a single instance, you can shift that function to be a classmethod:

class ChessBoard:
    def __init__(self):
        ...

    @classmethod
    @functools.cache
    def the_board(cls):
        return cls()

These ways are simpler than the usual gymnastics to implement singletons. They have the additional benefit that the users of the class clearly understand that they are accessing a global instance. This is another problem with Singleton: it gives you a class that lies. Calling the class looks like you are making new objects, but you are not.

These ways also let you still make new instances when you need to. Your ChessBoard tests will need to create many instances to keep your tests isolated from each other. The Singleton pattern makes this difficult and requires even more tricky machinations.

But I just need one!

So just make one. There’s no reason to complicate the class by adding Singleton enforcement to it.

But I need it everywhere in my code!

OK, use the_chess_board() wherever you were using ChessBoard().

But I still want a way to enforce “just one!”

The function manages the global instance. Why does it have to happen inside the class? You should separate the concept of what the class is from the idea that there should be only one.

But someone might make more instances!

Who? Document the right way to use the class. Make it clear and easy to do it the right way, and then let it be. You can’t defend against every bug, and it’s their program anyway.

But I thought globals were bad?

They are bad, but your Singleton was also a global: there was only one for the whole process, and it could be changed from anywhere. It wasn’t literally a Python global variable, but it had all the same bad qualities, just hidden behind some tricky meta-programming. If you’re going to have a global, be up-front about it.

But what about None and things like it?

True, some immutable value types can be singletons, but that’s not how people use the Singleton pattern, and how often are you writing a class like None?

But Singleton is in the Design Patterns book!

That doesn’t mean it’s a good idea. None of the examples in the book are true Singletons, they are all examples of programs that just happen to need only one instance. And the Design Patterns book was never meant to be prescriptive: it’s not telling you what to do, it’s describing what people have done.

Comments

[gravatar]

I’ve seen code with singletons that should have just been modules.

Modules handle being imported multiple times from multiple places while providing a single set of module-scoped functions and variables. They are singletons.

[gravatar]

A good way of avoiding polluting the global namespace with singletons is to use proper software design - eg. an object factory and a dependency injection mechanism. You’re almost there with the factory, but there is still the issue of recursive dependencies and object scoping/overridding (eg. for unit tests). I wrote an article about it a while ago (beware, my approach to python is very unpythonic) -https://oddbit.dev/post/python/di-python/

[gravatar]

I like doing it with modules. Define a class in a module, create an instance, then rename the instance to the same name as the class. Importing it gives you the same one every time. You can’t access the class by name anymore so that makes it harder to create new instances. Of course you can still do that if you set your mind to it, but the gymnastics needed to do so will stick out like a sore thumb.

[gravatar]

Reminds me of an age-old piece by Steve Yegge - Singleton Considered Stupid: https://sites.google.com/site/steveyegge2/singleton-considered-stupid

[gravatar]

Reply to Janet

importlib can be told to reload a module. This would mean the module global scoped variable would be reset.

Relying on a module being loaded only once is an avoidable unnecessary assumption.

A good test for functools.cache would be trying it out after a importlib reload of a module.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.