15

I'm looking for the right way to create a singleton class that accepts arguments in the first creation. My research lead me to 3 different ways:

Metaclass

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

class ASingleton(metaclass=Singleton):
    pass

__new__

class Singleton(object):
    instance = None
    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls, *args, **kwargs)
        return cls.instance

Decorator

def Singleton(myClass):
    instances={}
    def getInstance(*args, **kwargs):
        if myClass not in instances:
            instances[myClass] = myClass(*args, **kwargs)
        return instances[myClass]
    return getInstance

@Singleton
class SingletonTest(object):
    pass

All of them work fine, but when it comes to initiation (like using __init__ in normal class) I can't figure out the right way to implement it. The only solution I can think about is using the metaclass method in this way:

class Singleton(type):
    instance = None

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

class ASingleton(metaclass=Singleton):
    def __init__(self,j):
        self.j=j

I don't know if this is the correct way to create singleton that accepts arguments.

2
  • Possible duplicate of Creating a singleton in Python Commented Aug 17, 2018 at 14:15
  • that article was my main helpful tutorial, but as u can see, they didn't mention anything about "how to pass arguments or even how to setup the different methods) and that is my question Commented Aug 17, 2018 at 15:52

5 Answers 5

21

I've found out that the best way to implement Singleton is by using meta classes:

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]

# Python 2
class MyClass():
    __metaclass__= Singleton

# Python 3
class MyClass(metaclass=Singleton):
     pass
Sign up to request clarification or add additional context in comments.

5 Comments

I second this. I, too, think that metaclasses is the nicest way to implement a Singleton. However, have a look at metaclasses… I found them quite confusing in the beginning.
@Thomas sure, I cannot argue this ) Meta classes is tricky... But the Singleton is good use of it and is good start for understanding of metaclasses concept
True, Singleton's in Python actually made me understand Metaclasses :-)
what i understand from metaclass in singleton, it's like re-implement the method __call__() in the class that use "metaclass=singleton" to make sure to create one instance for that class. it make sense if you think in the way that the actual singleton class is the one that use (metaclass=singleton),
x = MyClass(5); y = MyClass(6) and here y will be configured with 5, not 6. This is not a problem with your solution, it's a problem with parametrized singleton.
5

In addition to @AndriyIvaneyko's answer, here is a thread-safe metaclass singleton implementation:

# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
# Whole idea for this metaclass is taken from: https://stackoverflow.com/a/6798042/2402281
import threading

class ThreadSafeSingleton(type):
    _instances = {}
    _singleton_lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        # double-checked locking pattern (https://en.wikipedia.org/wiki/Double-checked_locking)
        if cls not in cls._instances:
            with cls._singleton_lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super(ThreadSafeSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class YourImplementation(metaclass=ThreadSafeSingleton):
    def __init__(self, *args, **kwargs):
        pass  # your implementation goes here

Hope you find it useful!

1 Comment

you mean, this is will make sure to create only one instance by double check the value _instances by making the whole class as critical resource (semaphore approach)
2

I think the solution proposed by @tahesse can raise a dead lock. If the __init__ method contains another singleton than the lock won't be released.

Foe example :

class ThreadSafeSingleton(type):
    _instances = {}
    _singleton_lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        # double-checked locking pattern (https://en.wikipedia.org/wiki/Double-checked_locking)
        if cls not in cls._instances:
            with cls._singleton_lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super(ThreadSafeSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class YourImplementation1(metaclass=ThreadSafeSingleton):
    def __init__(self, *args, **kwargs):
        pass  # your implementation goes here

    def simple_method(self):
        return "this is a test"

class YourImplementation2(metaclass=ThreadSafeSingleton):

    def __init__(self, *args, **kwargs):
        self.your_implementation1 = YourImplementation1()
    def simple_method(self):
        print(self.your_implementation1.simple_method())

So I changed that solution a bit

class ThreadSafeSingleton(type):
    _instances = {}
    _singleton_locks: Dict[Any, threading.Lock] = {}

    def __call__(cls, *args, **kwargs):
        # double-checked locking pattern (https://en.wikipedia.org/wiki/Double-checked_locking)
        if cls not in cls._instances:
            if cls not in cls._singleton_locks:
                cls._singleton_locks[cls] = threading.Lock()
            with cls._singleton_locks[cls]:
                if cls not in cls._instances:
                    cls._instances[cls] = super(ThreadSafeSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class YourImplementation1(metaclass=ThreadSafeSingleton):
    def __init__(self, *args, **kwargs):
        pass  # your implementation goes here

    def simple_method(self):
        return "this is a test"

class YourImplementation2(metaclass=ThreadSafeSingleton):

    def __init__(self, *args, **kwargs):
        self.your_implementation1 =YourImplementation1()
    def simple_method(self):
        print(self.your_implementation1.simple_method())

Comments

2

The accepted answer did not work for me. What I wanted was every time I created an object of the singleton, it should have the values I passed to the constructor on the respective attributes. I used the __new__ method and in the __init__ I used the hasattr method to make sure the attributes that I don't want changed after they have been set remain unchanged.

class MySingleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = super(MySingleton, cls).__new__(cls)
        return cls._instance

    def __init__(self, somearg):
        self.somearg = somearg
        if not hasattr(self, 'common'):
            self.common = f'this should be set once: set at {datetime.now()}'

    obj1 = MySingleton('object 1')
    print(obj1.somearg, id(obj1), obj1.common)

    time.sleep(1)

    obj2 = MySingleton('object 2')
    print(obj2.somearg, id(obj2), obj2.common)

Comments

1

Check this out. The idea is to hash the instance key by args and kwargs.

import inspect
import threading

lock = threading.Lock()
class Singleton(type):
    _instances = {}
    _init = {}

    def __init__(cls, name, bases, dct):
        cls._init[cls] = dct.get('__init__', None)

    def __call__(cls, *args, **kwargs):
        init = cls._init[cls]
        if init is not None:
            args_list = list(args)
            for idx, arg in enumerate(args_list):
                args_list[idx] = str(arg)
            tmp_kwargs = {}
            for arg_key, arg_value in kwargs.items():
                tmp_kwargs[arg_key] = str(arg_value)
            key = (cls, frozenset(inspect.getcallargs(init, None, *args_list, **tmp_kwargs).items()))
        else:
            key = cls
        if key not in cls._instances:
            with lock:
                cls._instances[key] = super(SingletonArgs, cls).__call__(*args, **kwargs)
        return cls._instances[key]

class YourImplementation(metaclass=Singleton):
    def __init__(self, *args, **kwargs):
        pass  # your implementation goes here

2 Comments

This is really great! it only has one problem which is: if B inherits A which has Singleton as its meta, initializing B will now also initialize A instead of accessing A's Singleton (via super())
Also notice this doesn't take default arguments into account

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.