github.com/faif/python-patterns/blob/master/creational/borg.py
The pattern itself is relatively simple but if you have been working in a team of developers, singletons and they siblings (like borg) are always going to be a matter of discussion. Some people think simpletons are an anti-pattern others don't seem to have a problem implementing them.
I think to understand better this pattern, first you need to understand how singletons are implemented in Python. The idea behind a singleton is to have a single instance of a given class, no matter how many times I try to initialise it. The most common use out in the wild for this pattern is logging (or at least the most common I've seen).
Imagine the case where you want to log info, warnings, errors from a number of different classes (.py files) that are being used in your program. All of them want to log to a single file, a single point of entry. In this case, singleton will ensure this happens and you don't have two instances fighting to get into the log file first.
There's a number of ways to implement singletons in Python and plenty of "literature" already. I like this Stackoverflow question:
https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python
The one I prefer is the one that uses metaclasses which is going down a rabbit whole in its own and out of the scope of this post but very quickly, metaclasses define the way a class behaves. Look at the first response in that question for a very good explanation and the key, inheriting type. The confusing bit is that type is the function to figure out the type of an object but also the mother of all classes in Python.
Read more about metaclasses here (this is an advance but really interesting topic):
https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
Now, all of them share the same "trick" when it comes to keeping a single instance, they use a class variable to store the instance information.
_instances = {} or _instance = None
that way when the class is "tempted" to create a new instance, it will simply return the existing one.
Then, depending on the implementation (class, metaclass, etc) you will "intercept" the attempt of creating a new instance differently.
For the case of a class, you will intercept it with __new__ that handles the creation of new instances classes:
def __new__(class_, *args, **kwargs):
__new__ handles the object creation and that's why you need to return the class initialised (otherwise you would simply get None or whatever you're returning rather than the class object)
For a very good discussion on __new__ look here:
https://spyhce.com/blog/understanding-new-and-init
When using a metaclass, it's __call__ that it gets, well, called.
But the question is what's wrong with singletons and why are Borgs better? The answer is that they aren't. They are just different solutions for a "similar" problem:
- In Singleton, you always end up with the same instance
- In Borg, you end up always with the same state (but a different instance).
Depending on what you're doing this difference might be relevant or not and it will pretty much a case by case decision.
So looking through the Borg class code, the key here is to store the state in a class variable, in this case:
__shared_state = {}
so you can re-assign it to __dict__ which is the place where Python stores the class properties and variables. What you are doing here is referencing both dictionaries (__dict__ and __shared_state). Look around for explanation about deepcopy, shallowcopy and reference of objects in Python if you aren't familiar with them.
All the instances created from Borg, will share the __shared_state which will be referenced to __dict__ and all instances will end up with the same state.