pythonsingletondecoratorbase-classmetaclass

What is the best way of implementing singleton in Python


This question is not for the discussion of whether or not the singleton design pattern is desirable, is an anti-pattern, or for any religious wars, but to discuss how this pattern is best implemented in Python in such a way that is most pythonic. In this instance I define 'most pythonic' to mean that it follows the 'principle of least astonishment'.

I have multiple classes which would become singletons (my use-case is for a logger, but this is not important). I do not wish to clutter several classes with added gumph when I can simply inherit or decorate.

Best methods:


Method 1: A decorator

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

Pros

Cons

then x == y but x != t && y != t


Method 2: A base class

class Singleton(object):
    _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, BaseClass):
    pass

Pros

Cons


Method 3: A metaclass

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]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

Pros

Cons


Method 4: decorator returning a class with the same name

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

Pros

Cons


Method 5: a module

a module file singleton.py

Pros

Cons


Solution

  • You just need a decorator, depending on the python version:


    Python 3.2+

    Implementation

    from functools import lru_cache
    
    @lru_cache(maxsize=None)
    class CustomClass(object):
    
        def __init__(self, arg):
            print(f"CustomClass initialised with {arg}")
            self.arg = arg
    

    Usage

    c1 = CustomClass("foo")
    c2 = CustomClass("foo")
    c3 = CustomClass("bar")
    
    print(c1 == c2)
    print(c1 == c3)
    

    Output

    >>> CustomClass initialised with foo
    >>> CustomClass initialised with bar
    >>> True
    >>> False
    

    Notice how foo got printed only once


    Python 3.9+

    Implementation:

    from functools import cache
    
    @cache
    class CustomClass(object):
        ...