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:
the_chess_board = ChessBoard()
If you want centralized management of the instance, use a function to manage a global:
_the_chess_board = None
if _the_chess_board is None:
_the_chess_board = ChessBoard()
Or let functools do it for you:
@functools.cache # new in 3.9
If you still really want the class to manage a single instance, you can shift that function to be a classmethod:
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!
the_chess_board() wherever you were using
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.
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.
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/
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.
Reminds me of an age-old piece by Steve Yegge - Singleton Considered Stupid: https://sites.google.com/site/steveyegge2/singleton-considered-stupid
Add a comment: