Singleton Pattern in Python

Singleton Pattern in Python

ยท

3 min read

Singleton is a simple, controversial yet common design pattern that all developers should be aware of

If you're interested in loading this design pattern straight into your brain, understand why some developers consider it an anti-pattern well, that's great because we are going to cover that!

What is Singleton? ๐Ÿ’”

Singleton is when you force having only one (or limited) object(s) of its kind.

When to use it? ๐Ÿ•ถ

You use it when you want to force having only one (or limited) object(s) instantiated from a class.

It's useful when it comes to situations where you have limited global resources. like when your server has a database that should at most have one connection. If anyone mistakenly created a new instance of your database object, that might result in a new connection.

Implementation โŒจ

In python, this could be implemented by altering class behavior, using decorators, metaclasses, or a base class.

Pythonic - Base class

a pythonic way to implement Singleton is using a base class, but the constructor will be called on each instantiation

class Singleton(object):
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

__new__ which is a Magic Method similar to __init__, __new__ is called before the creation of an object and is responsible for returning a new instance of the class, whereas __init__ is called after the creation of an object and it's responsible for initializing the object aka Constructor.

Real Singleton - metaclass

Although the above approach works well, a real singleton shouldn't recall the constructor

we'll use a metaclass approach, if you are not familiar with metaclasses check this StackOverflow answer.

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None 

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)
        return cls.instance

And it could be used like

class DB(metaclass=Singleton):

    def __init__(self):
        self.id = random() * 100

    def conn_id(self):
        print("Connection ID: ", self.id)

first = DB()
sec = DB()
print(first == sec) # True; reference check
print(DB().conn_id() == first.conn_id() == sec.conn_id()) # True

The Religious war behind singleton โš”

Many argue over how Singleton is an anti-pattern or if it's just being misused.

Some say that it introduces global variables which lead to race conditions (in a multithreaded environment), the code becomes harder to test (as your code becomes tightly coupled with the singletons), it doesn't get freed from memory, it violates SRP (Single Responsibility Principle) and hides dependencies.

Whereas others say that Singletons aren't supposed to be used as global variables but should be used only to control the number of the object instantiations, still Singleton could be replaced by instantiating one object in the main() (the entry function of your app) and passing it to the needed modules aka Dependency Injection (DI).

It could be only a few edge cases to use Singleton like when you need a logger functionality, where it doesn't affect the code whether the logger is enabled or not, or when it's managed in an injectable container to handle its life cycle aka Dependency Injection (DI) just like how it's used in Angular or NestJS.

Conclusion

It is "acceptable" to use Singleton as long you are aware of the consequences, and it's not being overused.

I'm planning to cover the most common design patterns. if you are interested, you can follow me to get notified of any new articles that I publish in the future or you can check the below list of my published articles about design patterns:

ย