Singleton Pattern in Python

0

I was seeing this article on how to create singleton classes in Python (among many others), I found it unnecessary for so much programming technique for a really simple thing. Of course I may 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

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

Would not it be the same thing?

    
asked by anonymous 23.05.2018 / 19:22

2 answers

3

There are several ways to have a singleton in Python - if you do not mind using a specific method to get the singleton, it really can be one of the simplest approaches: that is "it's possible" to create another instance - you get the reference to the instance always from the same place (the method get_singleton ), you keep the same instance.

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

But - back to your example - the only problem with it is that its getInstance method is a normal instance method - so it already depends on an instance of the object being 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 singleton reference within the class itself, 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 want to, just add if not hasattr(cls, "single_instance"): to if not "single_instance" in cls.__dict__ - and that's it, a new singleton will be created for each subclass of "MySingletonManual" where the " get_singleton "is called)

In some cases, it is really prohibitive for more than one instance to be created - even if by accident, or even temporarily. In this case, as above, it is best to override the __new__ method. Moreover, it is possible to ensure that the creation of the singleton is even thread-safe, meeting the concerns of the 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 - if within __init__ prevents your body from running more than once through Python - and the code in __new__ ensures instantiation itself happens only once. The dick with the cls._lock attribute avoids the problem reported in the article above regarding the cost of locking a multithreading application: once the lock has fulfilled its function the first time, a "blank" object is put in place lock - all checks already will give the instance as existing, anyway - and there is more danger of a race condition.

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

    
24.05.2018 / 01:23
4

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

Both instances did not share the state because instance is an immutable type (save this term, it will be important later). Even if it is defined as a class attribute, it will be distinguished among all instances of the same class when it is modified. Therefore, when creating the second instance, self.instance will be null again, regardless of whether or not previous instances were created.

The simplest way to work around this problem would be to use a changeable type as a class attribute, 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

So when you run 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, 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, two of which will be used only to return the third. Imagine that you control flights at an airport is making people board on an airplane; it will only be one flight, but you are required to place two other aircraft to serve as a "corridor" for passengers to access the aircraft that will be used on the trip. Can you visualize the cost of it all?

To demonstrate this, it would be enough to separate the instance and execution operations from the getInstance method, by doing:

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, you need to answer a question: Do I really need only one object, class instance, or can I have multiple objects sharing state? p>

In case you can share the state with multiple distinct objects, simply use a class attribute with type changeable, similar to what 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 you could do 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 that when the message is changed in a , the change is reflected in b .

Now, if you need to always be the same object (do not just share the state), you can create a base class by setting 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 use 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

Notice that by setting b to a different value, the name attribute of a is also changed as it will be the same object.

    
23.05.2018 / 19:58