Singleton pattern in Python

Asked

Viewed 324 times

0

I was watching this article on how to create Singleton classes in Python (among many others), I found unnecessary so much programming technique for a really simple thing. Of course I could be wrong. But if I do this, for example

class TestSingleton:
    instance = None
    msg = "variável interna com dados padrão"

    def getInstance(self):
        if self.instance is None:
            self.instance = TestSingleton()
            return self.instance
        else:
            return self.instance
    def getConteudo(self):
        print self.msg
    def write(self, mensagem):
        self.msg = mensagem

obj = TestSingleton().getInstance()
obj.write("ola mundo")
obj.getConteudo()

obj2 = TestSingleton().getInstance()
obj2.getConteudo()

obj2.write("Que coisa !")
obj2.getConteudo() `

and then do

obj = TestSingleton().getInstance()
obj.write()

It wouldn’t be the same?

  • 1

    "unnecessary so much programming technique for a really simple thing" - a Singleton must ensure that one (and only one) instance of the class is created. In multi-threaded environments this is not as simple as it seems, and all this technique is needed to ensure that there is no more than one instance being created. This article explains why it’s not that simple. It uses examples in Java, but reported problems can happen in any language.

  • 1

    That’s an interesting article. Note that the Python specification ensures determinism in some respects - in particular, the code that assigns a reference to the instance to a variable will only be executed after all the instance initialization is complete. That is, in Python does not happen the biggest ghost foreseen in this article, which would be the reference to receive a memory address that would not yet be initialized.

  • 1

    I expanded the response to address the concerns of this article.

2 answers

5

No, not even close. If it were the same thing, when you executed obj2.getConteudo() the message to be displayed should be "hello world", not the default, because you changed the value of this attribute through obj when he did obj.write("ola mundo"). If they were Singleton, the objects should, necessarily, share exactly the same state.

The two instances did not share the state for the reason that instance is a guy immutable (keep this word, it will be important later). Even if it is defined as a class attribute, it will be distinguished between all instances of the same class when it is modified. Thus, when creating the second instance, self.instance will be null again, regardless of whether previous instances have been created or not.

The simplest way around this problem would be to use a type as a class attribute mutable, such as a list or dictionary. For example:

class TestSingleton:

    state = {
        'instance': None
    }

    msg = "variável interna com dados padrão"

    def getInstance(self):
        if self.state['instance'] is None:
            self.state['instance'] = TestSingleton()
        return self.state['instance']

    def getConteudo(self):
        print(self.msg)

    def write(self, mensagem):
        self.msg = mensagem

Thus, when executing the code:

obj = TestSingleton().getInstance()
obj.write("ola mundo")
obj.getConteudo()  # ola mundo

obj2 = TestSingleton().getInstance()
obj2.getConteudo()  # ola mundo

obj2.write("Que coisa !")
obj2.getConteudo()  # Que coisa !

obj.getConteudo()  # Que coisa !

That is, the objects will share the state because they will be exactly the same instance.

Still, this solution would be bad, because in this way there would be three distinct instances, and two will be used only to return the third. Imagine that you control flights at an airport are boarding people on an airplane; it will only be a flight, but you require them to put two other aircraft to serve as a "corridor" for passengers to be able to access the aircraft that will be used in the journey. Can you see the cost of it all?

To demonstrate this, it would be sufficient to separate the instance and execution operations from the method getInstance, making:

obj1 = TestSingleton()  # Aqui, obj1 seria a instância A
obj1 = obj1.getInstance()  # E aqui, obj1 receberia a instância C

obj2 = TestSingleton()  # Aqui, obj2 seria a instância B
obj2 = obj1.getInstance()  # E aqui, obj2 receberia a instância C

At this point, starting the path to the ideal solution, it is necessary to answer a question: i really need only one object, class instance, or I can have multiple objects sharing the state?

In case you can share the state with multiple distinct objects, just use a class attribute with changeable type, similar as was done above using the dictionary. For example:

class Foo:
    shared = {
        'message': 'Olá mundo'
    }

    def getMessage(self):
        return self.shared['message']

    def setMessage(self, message):
        self.shared['message'] = message

So something like:

a = Foo()
print(a.getMessage())  # Olá mundo
a.setMessage('Nova mensagem')

b = Foo()
print(b.getMessage())  # Nova mensagem

print('São o mesmo objeto:', b is a)  # São o mesmo objeto: False

The two objects will share the state defined in shared, so much so that when the message is changed in a, the change is reflected in b.

Already, if you need them to always be the same object (not just share the state), you can create a base class by defining the constructor method __new__:

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

And so you utilize inheritance in your class:

class Foo(Singleton):
    def __init__(self, name):
        self.name = name

For example:

a = Foo("Anderson")
print(a.name)  # Anderson

b = Foo("Woss")
print(b.name)  # Woss

print(a.name)  # Woss

print('São o mesmo objeto:', b is a)  # São o mesmo objeto: True

Note that when defining b with a different value, the attribute name of a is also changed as they will be the same object.

  • That reading the guy has to stop to read with pleasure... thank you for your time and explanation!

3


There are several ways to have a Singleton in Python - if you don’t mind using a specific method to get Singleton, it really can be one of the simplest approaches: i.e., "it’s possible" to create another instance - but if you take the reference to the instance always from the same place (the method get_singleton), you keep the same instance.

In order to actually "not possible" to create another instance, it would be necessary to customize the method __new__, or, possibly even make use of a metaclass, if it is necessary to prevent the method __init__ be called more than once (I prefer without metaclass, and with a "if" within the __init__).

But - back to his example - the only problem with him is that his method getInstance is a normal, instance method - so it already depends on whether there is an instance of the object to be called. And then the thing starts to knot. But if you turn it into a class method, then in fact it will create a single instance for the class, (and you can even keep a reference to Singleton within the class itself, so as not to pollute the module namespace):

class MeuSingletonManual:
    ...
    @classmethod
    def get_singleton(cls):
        if not hasattr(cls, "single_instance"):
            cls.single_instance = cls()
        return cls.single_instance

    ...

(This will not create a Singleton for subclasses, if you wish it, just raise the if not hasattr(cls, "single_instance"): for if not "single_instance" in cls.__dict__ - and ready, a new and unique English will be created for each subclass of "Meusingletonmanual" where the "get_singleton" method is called)

In some cases, it is actually prohibitive that it is possible to create more than one body - even by accident, or even temporarily. In that case, as it is above, it is better to override the method __new__. Moreover, it is possible to ensure that the creation of Singleton is even thread-safe, given the concerns of linked article in the comments.

class MockLock:

    def __enter__(self):
        pass
    def __exit__(self, exception, exception_value, trace):
        pass

class Singleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        with cls._lock:
            if not cls._instance:
                instance = super().__new__(cls)
                instance.__init__()
                cls._instance = instance
                cls._lock = MockLock()
        return cls._instance

   def __init__(self):
       if self.__class__._instance:
           return
       ...

Ready - the if within the __init__ prevents your body from being executed more than once by Python - and the code in __new__ ensures that the instantiation itself happens only once. The little dance with the attribute cls._lock avoids the problem reported in the above article regarding the cost of getting a lock in a multithreading application: after the lock has fulfilled its function the first time, a "blank" object is put in place of the lock - all checks already will give the instance as existing, anyway - and there is no more danger of a race condition.

(in a parallel note, in Python it is customary to use snake_case for methods, and not lowerFirstCamelCase)

  • Bueno, thank you so much for your explanations, thank you!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.