Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A thread safe implementation of singleton pattern in Python. Based on tornado.ioloop.IOLoop.instance() approach.
import threading
# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
__singleton_lock = threading.Lock()
__singleton_instance = None
@classmethod
def instance(cls):
if not cls.__singleton_instance:
with cls.__singleton_lock:
if not cls.__singleton_instance:
cls.__singleton_instance = cls()
return cls.__singleton_instance
if __name__ == '__main__':
class A(SingletonMixin):
pass
class B(SingletonMixin):
pass
a, a2 = A.instance(), A.instance()
b, b2 = B.instance(), B.instance()
assert a is a2
assert b is b2
assert a is not b
print('a: %s\na2: %s' % (a, a2))
print('b: %s\nb2: %s' % (b, b2))
@skwijeratne

This comment has been minimized.

Copy link

skwijeratne commented Sep 14, 2016

cls.singleton_instance = cls() doesnt work.
It should be this
cls.__singleton_instance = object.__new
(cls)

@hekejun

This comment has been minimized.

Copy link

hekejun commented Apr 26, 2017

I think "if not cls.__singleton_instance:" is duplicated, delete the first one is ok:

def instance(cls,*args,**kwargs):
with cls.__singleton_lock:
if not cls.__singleton_instance:
cls.__singleton_instance =cls()
return cls.__singleton_instance

@livioribeiro

This comment has been minimized.

Copy link

livioribeiro commented May 11, 2017

The if not cls.__singleton_instance is not duplicated, it is the double-checked locking pattern.

@jhbuhrman

This comment has been minimized.

Copy link

jhbuhrman commented Apr 16, 2020

What happens if somebody just calls another_a = A()? Then you have a second instance...

This could be prevented by modifying the instance() setting a temporary magic class attribute named _inside_instance to True (initially False) c/w adding a __new__() method, using the same lock (but should become Rlock (re-entrant) instead of Lock) checking whether _within_instance was set (i.e. existed). If not, raise an Exception (e.g. RuntimeError).

This all resulted in the following implementation (which I created before I spotted this one):

class SingletonMixin:
    """Mixin class to make your class a Singleton class."""

    _instance = None
    _rlock = RLock()
    _inside_instance = False

    @classmethod
    def instance(cls: Type[T], *args: Any, **kwargs: Any) -> T:
        """Get *the* instance of the class, constructed when needed using (kw)args.

        Return the instance of the class. If it did not yet exist, create it
        by calling the "constructor" with whatever arguments and keyword arguments
        provided.

        This routine is thread-safe. It uses the *double-checked locking* design
        pattern ``https://en.wikipedia.org/wiki/Double-checked_locking``_ for this.

        :param args: Used for constructing the instance, when not performed yet.
        :param kwargs: Used for constructing the instance, when not perfored yet.
        :return: An instance of the class
        """
        if cls._instance is not None:
            return cls._instance
        with cls._rlock:
            # re-check, perhaps it was created in the mean time...
            if cls._instance is None:
                cls._inside_instance = True
                try:
                    cls._instance = cls(*args, **kwargs)
                finally:
                    cls._inside_instance = False
        return cls._instance

    def __new__(cls, *args, **kwargs):
        """Raise Exception when not called from the :func:``instance``_ class method.

        This method raises RuntimeError when not called from the instance class method.

        :param args: Arguments eventually passed to :func:``__init__``_.
        :param kwargs: Keyword arguments eventually passed to :func:``__init__``_
        :return: the created instance.
        """
        if cls is SingletonMixin:
            raise TypeError(f"Attempt to instantiate mixin class {cls.__qualname__}")

        if cls._instance is None:
            with cls._rlock:
                if cls._instance is None and cls._inside_instance:
                    return super().__new__(cls, *args, **kwargs)

        raise RuntimeError(
            f"Attempt to create a {cls.__qualname__} instance outside of instance()"
        )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.