![]() |
|
[Tut] How to Create a Singleton in Python? - Printable Version +- Sick Gaming (https://sickgaming.net) +-- Forum: Programming (https://sickgaming.net/forum-76.html) +--- Forum: Python (https://sickgaming.net/forum-83.html) +--- Thread: [Tut] How to Create a Singleton in Python? (/thread-97170.html) |
[Tut] How to Create a Singleton in Python? - xSicKxBot - 09-10-2020 How to Create a Singleton in Python? Master coders behave like architects that connect and build upon various design patterns to create a functional whole. One of the most important design patterns is a singleton—a class that has only one instance. You may ask: How does that look like? Let’s have a look at the code implementing a singleton in our interactive code shell: https://repl.it/@finxter/singleton?lite=true Exercise: Try to create multiple instances of the singleton class. Can you do it? Let’s dive into a deeper understanding of the singleton. We’ll discuss this code in our first method, so keep reading! What’s a Singleton? A singleton is a class that has only one instance. All variables for the class point to the same instance. It is simple and straight forward to create and use and it is one of the design patterns described by the Gang of Four. After creating the first instance, all other creations point to the first instance created. It also solves the problem of having global access to a resource without using global variables. I like this concise definition from Head First Design Patterns: Quote:The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it. Why Would You Need a Singleton? If you are reading this, you likely already have a possible use. Singleton is one of the Gang of Four’s Creational patterns. Read on to determine if its a good candidate for the problem you need to solve. A singleton can be used to access a common resource like a database or a file. There is a bit of controversy on its use. In fact, the controversy could be described as outright singleton shaming. If that concerns you, I’ve listed some of the objections below with some links. In spite of all that, singletons can be useful and pythonic. From The Zen of Python (Pythonistas say Ohm):
Still the objections have merit and may apply to the code you are currently working on. And even if they don’t apply, understanding those objections may give you a better understanding of Object Oriented principals and unit testing. A singleton may be useful to control access to anything that changes globally when it is used. In addition to databases and files, a singleton may provide benefit for access to these resources:
A singleton can be used instead of using a global variable. Global variables are potentially messy. Singletons have some advantages over global variables. A singleton can be created with eager or lazy creation. Eager creation can create the resource when the program starts. Lazy creation will create the instance only when it is first needed. Global variables will use an eager creation whether you like it or not. Singletons do not pollute the global namespace. And finally, a singleton can be a part of a larger design pattern. It may be part of any of the following patterns:
Implementation The standard C# and Java implementations rely on creating a class with a private constructor. Access to the object is given through a method: Code: getInstance()Here is a typical lazy singleton implementation in Java: Code: public Singleton { private static Singleton theOnlyInstance; private Singleton() {} public static Singleton getInstance() { if (theOnlyInstance) == null){ theOnlyInstance = new Singleton() } return new Singleton(); }
}There are many ways to implement Singleton in Python. I will show all four first and discuss them below. Method 1: Use __new__ Code: class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) # Initialize here. No need to use __init__().. cls.val = 0 return cls._instance def business_method(self, val): self.val = val x = Singleton()
y = Singleton()
x.val = 42
x is y, y.valIt uses the Python dunder Code: __new__Code: __new__Pros:
Cons:
Method 2: A Decorator Code: def singleton(Cls): singletons = {} def getinstance(*args, **kwargs): if Cls not in singletons: singletons[Cls] = Cls(*args, **kwargs) return singletons[Cls] return getinstance @singleton
class MyClass: def __init__(self): self.val = 3 x = MyClass()
y = MyClass()
x.val = 42
x is y, y.val, type(MyClass)Pros
Cons
If you really want to use a decorator and must retain class definition, there is a way. You could use this library: Code: pip install singleton_decoratorThe library Code: singleton_decoratorCode: def singleton(Cls): class Decorated(Cls): def __init__(self, *args, **kwargs): if hasattr(Cls, '__init__'): Cls.__init__(self, *args, **kwargs) def __repr__(self) : return Cls.__name__ + " obj" __str__ = __repr__ Decorated.__name__ = Cls.__name__ class ClassObject: def __init__(cls): cls.instance = None def __repr__(cls): return Cls.__name__ __str__ = __repr__ def __call__(cls, *args, **kwargs): if not cls.instance: cls.instance = Decorated(*args, **kwargs) return cls.instance return ClassObject() @singleton
class MyClass(): pass x = MyClass()
y = MyClass()
x.val = 42
x is y, y.valThe output is: Code: (True, 42)Interactive Exercise: Run the following interactive memory visualization. How many singleton instances do you find? https://pythontutor.com/iframe-embed.html#code=def%20singleton%28Cls%29%3A%0A%20%20%20%20singletons%20%3D%20%7B%7D%0A%20%20%20%20def%20getinstance%28*args,%20**kwargs%29%3A%0A%20%20%20%20%20%20%20%20if%20Cls%20not%20in%20singletons%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20singletons%5BCls%5D%20%3D%20Cls%28*args,%20**kwargs%29%0A%20%20%20%20%20%20%20%20return%20singletons%5BCls%5D%0A%20%20%20%20%0A%20%20%20%20return%20getinstance%0A%0A%40singleton%0Aclass%20MyClass%3A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%203%0A%0Ax%20%3D%20MyClass%28%29%0Ay%20%3D%20MyClass%28%29%0Ax.val%20%3D%2042%0Ax%20is%20y,%20y.val,%20type%28MyClass%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false Method 3: Use Metaclass and Inherit From Type and Override __call__ to Trigger or Filter Instance Creation Code: class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class MyClass(metaclass=Singleton): pass x = MyClass()
y = MyClass()
x.val=4
x is y, y.valThe output is as follows: Code: (True, 4)Method 3 creates a new custom metaclass by inheriting from type. MyClass then assigns Singleton as its metadata: Code: class MyClass(metadata = Singleton):The mechanics of the Singleton class are interesting. It creates a dictionary to hold the instantiated singleton objects. The dict keys are the class names. In the overridden Code: __call__Code: super.__call__Code: __call__Pros
Cons
Method 4: Use a Base Class Code: class Singleton: _instance = None def __new__(class_, *args, **kwargs): if not isinstance(class_._instance, class_): class_._instance = object.__new__(class_, *args, **kwargs) return class_._instance class MyClass(Singleton): pass
x = MyClass()
y = MyClass()
x.val=4
x is y, y.valThe output is as follows: Code: (True, 4)Pros
In all four methods, an instance is created the first time it is asked for one. All calls after the first return the first instance. Singletons in a Threaded Environment If your Singleton needs to operate in a multi-threaded environment, then your Singleton method needs to be made thread-safe. None of the methods above is thread-safe. The vulnerable code is found between the check of an existing Singleton and the creation of the first instance: Code: if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls)Each implementation has a similar piece of code. To make it thread-safe, this code needs to be synchronized. Code: with threading.Lock(): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls)
This works fine and with the lock in place, the Singleton creation becomes thread-safe. Now, every time a thread runs the code, the Code: threading.Lock()If performance is not an issue, that’s great, but we can do better. The locking mechanism is expensive and it only needs to run the first time. The instance creation only happens once so the lock should happen at most one time. The solution is to place the lock after the check statement. Then add another check after the lock. Code: import threading
... if cls._instance is None: with threading.Lock(): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls)And that is how to use “Double-checked locking“. Thread-Safe Version of Method 1 Consider the following modification of method 1: Code: import threading
class Singleton: _instance = None def __new__(cls): if cls._instance is None: with threading.Lock(): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) # Initialize here. No need to use __init__().. cls.val = 0 return cls._instance def business_method(self, val): self.val = val x = Singleton()
y = Singleton()
x.val = 42
x is y, y.valThe output is: Code: (True, 42)To make it thread-safe, we added two lines of code. Each method could be made thread-safe in a similar way Alternatives to using a Singleton Use a Module as a Singleton (The Global Object Pattern) In Python, modules are single, unique, and globally available. The Global Object Pattern is recommended by the Python docs. It simply means to create a separate module and instantiate your object in the module’s global space. Subsequent references just need to import it. Use Dependency Injection Generally, this means using composition to provide objects to dependent objects. It can be implemented in countless ways but generally, put dependencies in constructors and avoid creating new instances of objects in business methods. The Problems With Singletons Of all 23 patterns in the seminal 1994 book Design Patterns, Singleton is the most used, the most discussed, and the most panned. It’s a bit of a rabbit hole to sift through the thousands of blogs and Stack Overflow posts that talk about it. But after all the Singleton hating, the pattern remains common. Why is that? It’s because conditions that suggest its use are very common: One database, one config file, one thread pool … The arguments against its use are best stated in some elegant (and old) blog posts that I cannot match. But I will give a summary and links for further reading. Concise Summary Paraphrased from Brian Button in Why Singletons are Evil:
Should You Use Singletons in Your Code? If you are asking yourself that based on the other peoples’ blogs, you are already in the rabbit hole. The word ‘should’ is not welcome in code design. Use singletons or not and be aware of possible problems. Refactor when there are problems. Possible Problems to Consider Tools are for people who know how to use them. In spite of all the bad stuff written about Singletons, people still use them because:
and / or
Avoid problems. Don’t be in group 2. Problems with Singletons are caused because they break the single responsibility rule. They do three things:
Further Study
Meta notes — Miško Hevery. Hevery worked at Google when he wrote these blogs. His blogs were readable, entertaining, informative, provocative, and generally overstated to make a point. If you read his blogs, be sure to read the comments. Singletons are Pathological Liars has a unit testing example that illustrates how singletons can make it difficult to figure out dependency chains and start or test an application. It is a fairly extreme example of abuse, but he makes a valid point: Quote:Singletons are nothing more than global state. Global state makes it so your objects can secretly get hold of things which are not declared in their APIs, and, as a result, Singletons make your APIs into pathological liars. Of course, he is overstating a bit. Singletons wrap global state in a class and are used for things that are ‘naturally’ global by nature. Generally, Hevery recommends dependency injection to replace Singletons. That simply means objects are handed their dependencies in their constructor. Where have all the singletons gone makes the point that dependency injection has made it easy to get instances to constructors that require them, which alleviates the underlying need behind the bad, global Singletons decried in the Pathological Liars. Meta notes — Brandon Rhodes The Singleton Pattern Quote:Python programmers almost never implement the Singleton Pattern as described in the Gang of Four book, whose Singleton class forbids normal instantiation and instead offers a class method that returns the singleton instance. Python is more elegant, and lets a class continue to support the normal syntax for instantiation while defining a custom Meta notes — J.B. Rainsberger Use your singletons wisely Know when to use singletons, and when to leave them behind Code: J.B. RainsbergerPublished on July 01, 2001 Automated unit testing is most effective when:
Singletons know too much There is one implementation anti-pattern that flourishes in an application with too many singletons: the I know where you live anti-pattern. This occurs when, among collaborating classes, one class knows where to get instances of the other. Towards acceptible singletons Singleton abuse can be avoided by looking at the problem from a different angle. Suppose an application needs only one instance of a class and the application configures that class at startup: Why should the class itself be responsible for being a singleton? It seems quite logical for the application to take on this responsibility, since the application requires this kind of behavior. The application, not the component, should be the singleton. The application then makes an instance of the component available for any application-specific code to use. When an application uses several such components, it can aggregate them into what we have called a toolbox. Meta notes — Mark Safayan Singleton anti pattern Quote:Instead of using this pattern, simply instantiate a single instance and propagate it to places that use the object as a parameter to make the dependency explicit. |