Introduction
Of all of the design patterns, the Singleton sample holds a novel place. It is simple, but is usually misunderstood. On this Byte, we’ll attempt to clarify the Singleton sample, perceive its core rules, and learn to implement it in Python. We’ll additionally discover learn how to create a Singleton utilizing a decorator.
The Singleton Sample
The Singleton sample is a design sample that restricts the instantiation of a category to a single occasion. That is helpful when precisely one object is required to coordinate actions throughout the system. The idea is typically generalized to methods that function extra effectively when just one object exists, or that limit the instantiation to a sure variety of objects.
The Singleton sample is part of the Gang of 4 design patterns and falls underneath the class of creational patterns. Creational patterns cope with object creation mechanisms, making an attempt to create objects in a way appropriate to the state of affairs.
Be aware: The Singleton sample is taken into account an anti-pattern by some because of its potential for misuse. It is vital to make use of it judiciously and solely when vital.
Making a Singleton in Python
Python does not natively assist the Singleton sample, however there are a number of methods to create one. Here is a easy instance:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = tremendous(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
Within the above code, we override the __new__
methodology. This methodology known as earlier than __init__
when an object is created. If the Singleton class’s _instance
attribute is None
, we create a brand new Singleton object and assign it to _instance
. If _instance
is already set, we return that as a substitute.
Utilizing this method successfully solely permits the Singleton
class to be instantiated as soon as. You may then add any properties or strategies to this class that you simply want.
Utilizing a Decorator
One other technique to create a Singleton in Python is by utilizing a decorator. Decorators enable us to wrap one other operate with a purpose to lengthen the habits of the wrapped operate, with out completely modifying it.
Here is how we will create a Singleton utilizing a decorator:
def singleton(cls):
cases = {}
def wrapper(*args, **kwargs):
if cls not in cases:
cases[cls] = cls(*args, **kwargs)
return cases[cls]
return wrapper
@singleton
class Singleton:
cross
Within the above code, the @singleton
decorator checks if an occasion of the category it is adorning exists within the cases
dictionary. If it does not, it creates one and provides it to the dictionary. If it does exist, it merely returns the prevailing occasion.
Utilizing a Base Class
Making a singleton utilizing a base class is an easy strategy. Right here, we outline a base class that maintains a dictionary of occasion references. At any time when an occasion is requested, we first examine if the occasion already exists within the dictionary. If it does, we return the prevailing occasion, in any other case, we create a brand new occasion and retailer its reference within the dictionary.
Here is how one can implement a singleton utilizing a base class in Python:
class SingletonBase:
_instances = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instances:
occasion = tremendous().__new__(cls)
cls._instances[cls] = occasion
return cls._instances[cls]
class Singleton(SingletonBase):
cross
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
Within the above code, SingletonBase
is the bottom class that implements the singleton sample. Singleton
is the category that we wish to make a singleton.
Utilizing a Metaclass
A metaclass in Python is a category of a category, that means a category is an occasion of its metaclass. We are able to use a metaclass to create a singleton by overriding its __call__
methodology to regulate the creation of cases.
Here is how one can implement a singleton utilizing a metaclass in Python:
class SingletonMeta(kind):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
occasion = tremendous().__call__(*args, **kwargs)
cls._instances[cls] = occasion
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
cross
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
Within the above code, SingletonMeta
is the metaclass that implements the singleton sample. Singleton
is the category that we wish to make a singleton.
Use Circumstances
Singletons are helpful when it’s good to management entry to a useful resource or when it’s good to restrict the instantiation of a category to a single object. That is usually helpful in situations reminiscent of logging, driver objects, caching, thread swimming pools, and database connections.
Singleton sample is taken into account an anti-pattern by some because of its international nature and the potential for unintended unintended effects. Make sure to use it solely when vital!
Singletons and Multithreading
When coping with multithreading, singletons could be tough. If two threads attempt to create an occasion on the identical time, they may find yourself creating two completely different cases. To forestall this, we have to synchronize the occasion creation course of.
Here is how one can deal with singleton creation in a multithreaded atmosphere:
Try our hands-on, sensible information to studying Git, with best-practices, industry-accepted requirements, and included cheat sheet. Cease Googling Git instructions and truly be taught it!
import threading
class SingletonMeta(kind):
_instances = {}
_lock: threading.Lock = threading.Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
occasion = tremendous().__call__(*args, **kwargs)
cls._instances[cls] = occasion
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
cross
def test_singleton():
s1 = Singleton()
print(s1)
threads = [threading.Thread(target=test_singleton) for _ in range(10)]
for thread in threads:
thread.begin()
for thread in threads:
thread.be a part of()
Within the above code, we use a lock to make sure that just one thread can create an occasion at a time. This prevents the creation of a number of singleton cases in a multithreaded atmosphere.
Widespread Pitfalls
Whereas singletons is usually a highly effective instrument in your Python programming toolkit, they don’t seem to be with out their pitfalls. Listed here are a couple of frequent ones to bear in mind:
-
International Variables: Singleton can generally be misused as a worldwide variable. This may result in issues because the state of the singleton could be modified by any a part of the code, resulting in unpredictable habits.
-
Testability: Singletons could make unit testing tough. Since they preserve state between calls, a check may probably modify that state and have an effect on the end result of different assessments. This is the reason it is vital to make sure that the state is reset earlier than every check.
-
Concurrency Points: In a multithreaded atmosphere, care have to be taken to make sure that the singleton occasion is barely created as soon as. If not correctly dealt with, a number of threads may probably create a number of cases.
Here is an instance of how a singleton could cause testing points:
class Singleton(object):
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = tremendous(Singleton, cls).__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
s1.x = 5
print(s2.x)
On this case, in case you had been to check the habits of Singleton
and modify x
, that change would persist throughout all cases and will probably have an effect on different assessments.
Conclusion
Singletons are a design sample that restricts a category to a single occasion. They are often helpful in situations the place a single shared useful resource, reminiscent of a database connection or configuration file, is required. In Python, you’ll be able to create a singleton utilizing numerous strategies reminiscent of decorators, base courses, and metaclasses.
Nonetheless, singletons include their very own set of pitfalls, together with misuse as international variables, difficulties in testing, and concurrency points in multithreaded environments. It is vital to concentrate on these points and use singletons judiciously.