Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 439
Joseph Avatar asked Jul 07 '15 13:07

Joseph


2 Answers

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")
like image 120
WorldSEnder Avatar answered Sep 22 '22 11:09

WorldSEnder


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'
like image 26
martineau Avatar answered Sep 20 '22 11:09

martineau