Joseph
Joseph

Reputation: 1091

Why Singleton in python calls __init__ multiple times and how to avoid it?

i've this implementation of singleton pattern in python, but i've noticed that the init is called, uselessly, everytime i call MyClass, despite the same instance is returned.

How can i avoid it?

class Test(object):
    def __init__(self, *args, **kwargs):
        object.__init__(self, *args, **kwargs)

class Singleton(object):
  _instance = None

  def __new__(cls):
    if not isinstance(cls._instance, cls):
        cls._instance = object.__new__(cls)
    return cls._instance

class MyClass(Singleton):
    def __init__(self):
        print("should be printed only 1 time")
        self.x=Test()
        pass

a = MyClass() # prints: "should be printed only 1 time"
b = MyClass() # prints ( again ): "should be printed only 1 time"

print(a,b) # prints: 0x7ffca6ccbcf8 0x7ffca6ccbcf8
print(a.x,b.x) # prints: 0x7ffca6ccba90 0x7ffca6ccba90

Upvotes: 7

Views: 8008

Answers (5)

Valentin Wagnon
Valentin Wagnon

Reputation: 11

It's never too late i guess.. The answer is in fact in the question.

i've noticed that the init is called, uselessly, everytime i call MyClass

So, just remove access to it (and probably from memory)...

class Singleton:

    _instance = None
    
    def __new__(cls, *args, **kwargs):

        print('__new__()')

        if cls._instance is None:
            cls._instance = super().__new__(cls) # Everything is normal python, no stress !

        elif cls.__init__.__name__ == '__init__':
            print("No need __init__ anymore, sorry, goodbye !")
            cls.__init__ = lambda *args: None # The __name__ of __init__ function is "<lambda>" now.

        print(cls._instance)

        return cls._instance # After this return, python call internally __init__(). Even if you set cls._instance to None, the __init__ function is not present anymore.

class App(Singleton):

    def __init__(self, number):
        print(f"Init only one time with number {number} !")

if __name__ == "__main__":

    App(24)
    App(53)
    App(63)
    App(63)

Output:

__new__()
<App object at 3ffe5e90>
Init only one time with number 24 !
__new__()
No need __init__ anymore, sorry, goodbye !
<App object at 3ffe5e90>
__new__()
<App object at 3ffe5e90>
__new__()
<App object at 3ffe5e90>

Upvotes: 0

Vikalp Veer
Vikalp Veer

Reputation: 437

Another way to create a singleton object would be to have another class method say get_instance which would ensure there is a singleton object for your class .. here is the snippet :

class Singleton(object):
    __instance = None

    @classmethod
    def get_instance(cls):
        if not cls.__instance:
            Singleton()
        return cls.__instance

    def __init__(self):
        if Singleton.__instance is None:
            Singleton.__instance = self
            print("Object initialized")

singleton_1 = Singleton.get_instance()
singleton_2 = Singleton.get_instance()

if singleton_1 is singleton_2:
    print("Same")
else:
    print("Different")

The above snippet produces following output :

Object initialized
Same

Upvotes: 0

martineau
martineau

Reputation: 123531

Another simple, but totally viable way to implement what you want, which doesn't require super or meta classes, is to just make your class a Python module, which are essentially Singleton objects.

Here's what I mean:

myclass.py:

class Test(object):
    pass

class MyClass(object):
    def __init__(self):
        print("in MyClass.__init__, should be printed only 1 time")
        self.x = Test()

    def __call__(self, *args, **kwargs):
        classname = type(self).__name__
        return globals()[classname]

MyClass = MyClass()

client.py:

from myclass import MyClass

a = MyClass()
b = MyClass()

print(a, b)
print(a.x, b.x)

Output:

in MyClass.__init__, should be printed only 1 time
<myclass.MyClass object at 0x02200B30> <myclass.MyClass object at 0x02200B30>
<myclass.Test object at 0x02200C10> <myclass.Test object at 0x02200C10>

It's possible to derive a subclass from MyClass, but you'd have to do it something like this:

class Derived(type(MyClass)):
    def __init__(self):
        print("in Derived.__init__, should be printed only 1 time")

Derived = Derived()

Afterwards you could add this to 'client.py':

from myclass import Derived

a = Derived()
b = Derived()

print(a,b)
print(a.x,b.x)  # AttributeError: 'Derived' object has no attribute 'x'

Upvotes: 5

WorldSEnder
WorldSEnder

Reputation: 5054

The problem is that __new__ doesn't return an object, it returns an unitialized object on which __init__ is called afterwards.

You can't avoid that at all. What you can do is the following(using metatypes):

class Singleton(type):
    def __init__(self, name, bases, mmbs):
        super(Singleton, self).__init__(name, bases, mmbs)
        self._instance = super(Singleton, self).__call__()

    def __call__(self, *args, **kw):
        return self._instance

class Test(metaclass = Singleton):
    # __metaclass__ = Singleton # in python 2.7
    def __init__(self, *args, **kw):
        print("Only ever called once")

Upvotes: 13

jonrsharpe
jonrsharpe

Reputation: 122151

__init__, if implemented, is always called on whatever gets returned by __new__; in general, you should be implementing one or the other, not both. You could implement __new__ on MyClass, instead, and initialise the x attribute only if it doesn't already exist:

class MyClass(Singleton):

    def __new__(cls):
        inst = super(MyClass, cls).__new__(cls)
        if not hasattr(inst, 'x'):
            print("should be printed only 1 time")
            inst.x = Test()
        return inst

In use:

>>> a = MyClass()
should be printed only 1 time
>>> b = MyClass()
>>> a is b
True
>>> a.x is b.x
True

Upvotes: 0

Related Questions