This article describes the main strategies to implement the Singleton design pattern in Python. I am going to skip the whole very subjective “use and do not use singleton” conundrum.
Introduction
There are developers who simply call to “ban” this design pattern. Personally, I think while it does not respect the single responsibility principle, the singleton may still be useful in situations where you need to control a concurrent access to a shared resource, or want to use it to implement other design patterns.
Definition
On page 144 of the Gof4 book, the singleton design pattern’s stated intent is to “ensure a class only has one instance”.
Implementation
The singleton design pattern must be implemented in such a way that the sole class instance must be reachable via a global access point -namely a function, and can be extensible by the means of inheritence. Below are listed some methodologies Python uses to concrete it.
Module
The easiest approach to fulfill the singteton design pattern goal is to keep the global state in private variables within module which, in turn, must provide a way to access them by means of public functions.
As an example, here is singleton.py file:
|
|
The main application makes use of it as follows:
|
|
This is a practical approach but the main trivial issue with it is that there is no way to re-use the code through inheritance. This means the module approach does not fully respect the singleton design pattern. One can also argue this may sound a “coward” implementation in that may be most of other high level programming languages could do the same. This can also anger purists in that it is neither pythonic nor does it take advantage of the object-oriented concepts.
Traditional
To remedy to the drawbacks of the previous approach, there is a traditional way to implement the singleton design pattern in Python:
|
|
Here an advantage is taken from the special static method __new__() which helps to control instance creation. Unlike __init__(), it always takes as a parameter the class cls in which it is invoked and returns an instance of it.
The Python built-in function hasattr() checks if the class cls has an attribute called ‘instance’. If that is not the case, the ‘instance’ attribute is created and returned.
A better explanation of this implementation can be through the use of TraditionalSingleton() class:
|
|
Two instances of TraditionalSingleton() class are created. In reality, they are the same instance:
|
|
Hence modifying data_test variable in any instance will be automatically mirrored in the other instance:
|
|
The main issue of this approach remains its inconvenience to use inheritance.
Decoractor
There are many ways to implement the singleton using a decorator. Here is one of the implementations I found on StackOverflow:
|
|
Here is how to force a class to be a singleton:
|
|
This is how to instantiate SingletonClass() class:
|
|
The main drawback with this implementation is that it is not thread-safe.
Metaclass
An object is an instance of a class which is itself an instance of a metaclass:
|
|
A class can be declared singleton like follows:
|
|
While this approach copes well with inheritance, I still personally prefer the decorator approach which is handy.
Borg
The Borg approach states that the identity of the instantiated objects is not important. Borg focus on the state which must be common and shared. This allows the creation of as many instances as needed, but any of these instances which modifies the state mirrors the modification to all the remaining instances. This is why Borg is also referred as monostate design pattern:
|
|
Any class which inherits from the above one becomes a Borg class itself:
|
|
Alex Martelli, the author of Borg, considers this as a design pattern of its own. While some others consider it just a special singleton implementation. I personally belong to this later group.
Conclusion
I have gone briefly gone through the main approaches Python uses to implement it. You can also design your own implementation. It is up to you the developer to decide which one is more suitable to your case.