The absolute statement is false.

Application of Singleton Pattern in Python

(NOTE: this article includes content translated by a machine)

Singleton Pattern

Ensures that a class has only one instance, and provides a global access point to it.


Let’s take a look at the Singleton pattern in tornado.IOLoop:

class IOLoop(object):

    @staticmethod
    def instance():
        """Returns a global `IOLoop` instance.

        Most applications have a single, global `IOLoop` running on the
        main thread.  Use this method to get this instance from
        another thread.  To get the current thread's `IOLoop`, use `current()`.
        """
        if not hasattr(IOLoop, "_instance"):
            with IOLoop._instance_lock:
                if not hasattr(IOLoop, "_instance"):
                    # New instance after double check
                    IOLoop._instance = IOLoop()
        return IOLoop._instance

Why double check here?


Simple Singleton Pattern

Let’s look at the code:

class Singleton(object):

    @staticmethod
    def instance():
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton()
        return Singleton._instance

In Python, we can play around with the real constructor __new__:

class Singleton(object):

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

This situation seems fine, but it can’t guarantee to work well in a multithreaded environment, as shown in the following figure: singleton.png After introducing multithreading, it’s clear that this approach is not viable.


Locking for Thread Synchronization

The code after locking:

import threading

class Singleton(object):

    _instance_lock = threading.Lock()

    @staticmethod
    def instance():
        with Singleton._instance_lock:
            if not hasattr(Singleton, '_instance'):
                Singleton._instance = Singleton()
        return Singleton._instance

This indeed solves the multithreading situation, but we only need to lock when instantiating. At other times, Singleton._instance already exists and doesn’t need to be locked, but other threads that want to get the Singleton instance still have to wait. The presence of a lock clearly reduces efficiency and causes performance loss.


Global Variables

In languages like Java/C++, we can use global variables to solve the problems brought by locking (synchronization):

class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

}

In Python, it’s like this:

class Singleton(object):
    pass

singleton = Singleton()  # Directly use the module-level global variable

But if the class occupies a large amount of resources, it is very uneconomical to have the instance exist before it is used…


Conclusion

So, the double check Singleton pattern like tornado.IOLoop.instance() appeared. In a multithreaded environment, it neither suffers from the performance degradation brought by synchronization (locking) nor wastes resources due to direct instantiation of global variables.


Supplement on June 25 (using decorator):

https://wiki.python.org/moin/PythonDecoratorLibrary#Singleton

import functools

def singleton(cls):
    ''' Use class as singleton. '''

    cls.__new_original__ = cls.__new__

    @functools.wraps(cls.__new__)
    def singleton_new(cls, *args, **kw):
        it =  cls.__dict__.get('__it__')
        if it is not None:
            return it

        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it

    cls.__new__ = singleton_new
    cls.__init_original__ = cls.__init__
    cls.__init__ = object.__init__

    return cls

#
# Sample use:
#

@singleton
class Foo:
    def __new__(cls):
        cls.x = 10
        return object.__new__(cls)

    def __init__(self):
        assert self.x == 10
        self.x = 15

assert Foo().x == 15
Foo().x = 20
assert Foo().x == 20

https://wiki.python.org/moin/PythonDecoratorLibrary#The_Sublime_Singleton

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance


#
# Sample use
#

@singleton
class Highlander:
    x = 100
    # Of course you can have any attributes or methods you like.

Highlander() is Highlander() is Highlander #=> True
id(Highlander()) == id(Highlander) #=> True
Highlander().x == Highlander.x == 100 #=> True
Highlander.x = 50
Highlander().x == Highlander.x == 50 #=> True

Update on August 27:

import threading

class Singleton(object):

    _thread_lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with cls._thread_lock:
                if not hasattr(cls, '_instance'):
                    # cls._instance = super(Singleton, cls).__new__(cls)
                    cls._instance = super(Singleton, cls).__new__(
                        cls, *args, **kwargs)
        return cls._instance

    def __init__(self, bar):
        self._bar = bar

    def print_arg(self):
        print(self._bar)

class SingletonGood(object):

    _thread_lock = threading.Lock()

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with cls._thread_lock:
                if not hasattr(cls, '_instance'):
                    # cls._instance = super(SingletonGood, cls).__new__(cls)
                    # cls._instance.__init__(*args, **kwargs)
                    cls._instance = cls(*args, **kwargs)
        return cls._instance

    def __init__(self, bar):
        self._bar = bar

    def print_arg(self):
        print(self._bar)

if __name__ == '__main__':
    obj1 = Singleton('bar')
    obj2 = Singleton('foobar')
    assert id(obj1) == id(obj2)
    obj1.print_arg()  # foobar
    obj2.print_arg()  # foobar

    print('-' * 10)

    obj3 = SingletonGood.get_instance('bar')
    obj4 = SingletonGood.get_instance('foobar')
    assert id(obj3) == id(obj4)
    obj3.print_arg()  # bar
    obj4.print_arg()  # bar