2

I was trying to understand how to create a Singleton class in Python. Below is how i attempted

class Singleton(object):
    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_)
        return class_._instance

class MyClass(Singleton):
    num_of_instances = 0
    def __init__(self, real = 5, imaginary = 6):
        self.real = real
        self.imaginary = imaginary
        MyClass.num_of_instances += 1

a = MyClass(10, 20)
print(a.real)
print(a.imaginary)
b = MyClass()

print(MyClass.num_of_instances)  # 2

Ideally __new__() calls __init__() with the object instance, but in the above case when I am trying to create second object b, __new__ won't be called because an instance of MyClass already exits then why does the print statement printing num_of_instances print 2?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
ankit
  • 1,499
  • 5
  • 29
  • 46

1 Answers1

4

__new__ is called for every MyClass(...) call. If it didn't get called, it would not be able to return the singleton instance.

And when the __new__ method returns an object and that object is an instance of the cls argument passed to __new__ (or a subclass), then the __init__ method is also called.

So, for each MyClass(...) call, __new__ is called. The __new__ method always returns an instance of the current class, so __init__ is called, every time. It doesn't matter here that it is the same instance each time.

From the __new__ method documentation:

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __new__().

You can see this happen if you add some print() calls in the methods:

>>> class Singleton(object):
...     _instance = None
...     def __new__(class_, *args, **kwargs):
...         print(f'Calling {class_!r}(*{args!r}, **{kwargs!r})')
...         if not isinstance(class_._instance, class_):
...             print(f'Creating the singleton instance for {class_!r}')
...             class_._instance = object.__new__(class_)
...         return class_._instance
...
>>> class MyClass(Singleton):
...     num_of_instances = 0
...     def __init__(self, real=5, imaginary=6):
...         print(f'Calling {type(self)!r}.__init__(self, real={real!r}, imaginary={imaginary!r})')
...         self.real = real
...         self.imaginary = imaginary
...         MyClass.num_of_instances += 1
...
>>> a = MyClass(10, 20)
Calling <class '__main__.MyClass'>(*(10, 20), **{})
Creating the singleton instance for <class '__main__.MyClass'>
Calling <class '__main__.MyClass'>.__init__(self, real=10, imaginary=20)
>>> b = MyClass()
Calling <class '__main__.MyClass'>(*(), **{})
Calling <class '__main__.MyClass'>.__init__(self, real=5, imaginary=6)

You can't prevent the automatic __init__ call, at least not without overriding something else. If you want to avoid __init__ being called each time, you have some options:

You don't have to use an __init__ method on the subclass. You could invent your own mechanism, __new__ could look for a __singleton_init__ method and call that:

class Singleton(object):
    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_)
            if hasattr(class_._instance, '__singleton_init__'):
                class_._instance.__singleton_init__(*args, **kwargs)`
        return class_._instance

or your __init__ method could check if there are already attributes set in vars(self) (or self.__dict__) and just not set attributes again:

class MyClass(Singleton):
    def __init__(self, real=5, imaginary=6):
        if vars(self):
            # we already set attributes on this instance before
            return
        self.real = real
        self.imaginary = imaginary

The __new__ and __init__ logic is implemented in type.__call__; you could create a metaclass that overrides that logic. While you could simply call __new__ only (and leave everything as is), it makes sense to make the metaclass responsible for handling the Singleton pattern:

class SingletonMeta(type):
    def __new__(mcls, *args, **kwargs):
        cls = super().__new__(mcls, *args, **kwargs)
        cls._instance = None
        return cls

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

then use this not as a base class but with metaclass=.... You can create an empty base class if that's easier:

class Singleton(metaclass=SingletonMeta):
    pass

class MyClass(Singleton):
    # ...

The above will call __new__ on the class, optionally followed by __init__ on the resulting instance, just once. The SingletonMeta.__call__ implementation then, forever after, returns the singleton instance without further calls:

>>> class SingletonMeta(type):
...     def __new__(mcls, *args, **kwargs):
...         cls = super().__new__(mcls, *args, **kwargs)
...         cls._instance = None
...         return cls
...     def __call__(cls, *args, **kwargs):
...         print(f'Calling {cls!r}(*{args!r}, **{kwargs!r})')
...         if cls._instance is None:
...             cls._instance = super().__call__(*args, **kwargs)
...         return cls._instance
...
>>> class Singleton(metaclass=SingletonMeta):
...     pass
...
>>> class MyClass(Singleton):
...     def __init__(self, real=5, imaginary=6):
...         print(f'Calling {type(self)!r}.__init__(self, real={real!r}, imaginary={imaginary!r})')
...         self.real = real
...         self.imaginary = imaginary
...
>>> a = MyClass(10, 20)
Calling <class '__main__.MyClass'>(*(10, 20), **{})
Calling <class '__main__.MyClass'>.__init__(self, real=10, imaginary=20)
>>> MyClass()
Calling <class '__main__.MyClass'>(*(), **{})
<__main__.MyClass object at 0x10bf33a58>
>>> MyClass() is a
Calling <class '__main__.MyClass'>(*(), **{})
True
>>> MyClass().real
Calling <class '__main__.MyClass'>(*(), **{})
10
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @Martjin In this scenario how can avoid reinitialization of the object ? – ankit Oct 24 '18 at 12:20
  • @ankit: added that to the answer. – Martijn Pieters Oct 24 '18 at 12:22
  • *"You can't prevent the automatic `__init__` call."* Umm, that's pretty misleading. You can't avoid the automatic `__init__` call ***if*** you implement your singleton logic in `__new__`. There are other solutions that don't have this problem with `__init__` being called. – Aran-Fey Oct 24 '18 at 12:27
  • @Aran-Fey: you can't prevent the `__init__` call being called when `__new__` returns an object for which `isinstance(cls, returnvalue)` is true. I never made claims about different singleton approaches. *pretty misleading* is a rather harsh tone there, buddy! – Martijn Pieters Oct 24 '18 at 13:23
  • @Aran-Fey: and I've now added an alternative. – Martijn Pieters Oct 24 '18 at 13:40
  • Well, I thought the "you can't prevent it" might sound like generalization to some people. Of course you and I know that it's only true in the context of `__new__`, but less experienced python users might not. So I thought it was worth clarifying. It's good that you've added an alternative. – Aran-Fey Oct 24 '18 at 13:48
  • @MartijnPieters Need your help here please https://stackoverflow.com/questions/72456961/python-class-singleton-pass-the-multiple-argument-to-check-comparison – Kunal Joshi Jun 01 '22 at 14:34