Separating __call__ into __new__ and __init__

Question:

I am trying to write some metaclass for a special case of a SingletonMeta.

This is not a question about Singletons. It is about using metaclasses in Python.

I need to control __new__ and __init__ of the Singleton classes (the instances of SingletonMeta) separately, but I was not able to do so. All I managed to do was call them both together.

A dumbed down version of my code is currently only using cls.__call__, which I want to separate into __new__ and __init__.

Problem is I don’t know how parameters are passed within the default __call__ into __new__, and possibly other knowledge gaps.


My code, declaring a SingletonMeta metaclass, and a Singleton concrete class which is its instance.

class SingletonMetaMeta(type):
    def __new__(mcs, *args, **kwargs):  # args passed on import time with class Singleton(metaclass=NonInheritableSingletonMetaMeta)
        print(f"SingletonMeta __new__, args:{args}, kwargs: {kwargs}")
        cls = super().__new__(mcs, *args, **kwargs)
        return cls

    def __init__(cls, *args, **kwargs):
        print(f"SingletonMetaMeta __init__, args:{args}, kwargs: {kwargs}")
        super(SingletonMetaMeta, cls).__init__(*args, **kwargs)
        cls.__instance = None

    def __call__(cls, *args, **kwargs):
        print(f"SingletonMeta __call__, args:{args}, kwargs: {kwargs}")
        if cls.__instance is None:
            cls.__instance = super(SingletonMetaMeta, cls).__call__(*args, **kwargs)
        return cls.__instance


class Singleton(metaclass=SingletonMetaMeta):
    def __new__(cls, *args, **kwargs):
        print(f"Singleton __new__, args:{args}, kwargs: {kwargs}")
        self = super().__new__(cls)
        return self

    def __init__(self, *args, **kwargs):
        print(f"Singleton __init__, args:{args}, kwargs: {kwargs}")
        super().__init__()

    def __call__(self, *args, **kwargs):
        print(f"Singleton __call__, args:{args}, kwargs: {kwargs}")


print("class created")

instance = Singleton(1)

print("instance created")

outputs

SingletonMeta __new__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f790a09d5e0>, '__init__': <function Singleton.__init__ at 0x7f790a09d670>, '__call__': <function Singleton.__call__ at 0x7f790a09d700>, '__classcell__': <cell at 0x7f79238e61c0: empty>}), kwargs: {}
SingletonMetaMeta __init__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f790a09d5e0>, '__init__': <function Singleton.__init__ at 0x7f790a09d670>, '__call__': <function Singleton.__call__ at 0x7f790a09d700>, '__classcell__': <cell at 0x7f79238e61c0: SingletonMetaMeta object at 0x5631858c8fb0>}), kwargs: {}
class created
SingletonMeta __call__, args:(1,), kwargs: {}
Singleton __new__, args:(1,), kwargs: {}
Singleton __init__, args:(1,), kwargs: {}
instance created

showing super(SingletonMetaMeta, cls).__call__(*args, **kwargs) in SingletonMetaMeta‘s __call__ indeed calls the classes’ __new__ then __init__.

How can I interfere with that process? I want to manually call them myself.


My attempt

Trying to replace cls.__instance = super(SingletonMetaMeta, cls).__call__(*args, **kwargs)

with

            instance = cls.__new__(*args, **kwargs)
            instance.__init__(*args, **kwargs)

gives

SingletonMeta __new__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f5c0c9d35e0>, '__init__': <function Singleton.__init__ at 0x7f5c0c9d3670>, '__call__': <function Singleton.__call__ at 0x7f5c0c9d3700>, 'foo': <function Singleton.foo at 0x7f5c0c9d3790>, '__classcell__': <cell at 0x7f5c2621d1c0: empty>}), kwargs: {}
SingletonMetaMeta __init__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f5c0c9d35e0>, '__init__': <function Singleton.__init__ at 0x7f5c0c9d3670>, '__call__': <function Singleton.__call__ at 0x7f5c0c9d3700>, 'foo': <function Singleton.foo at 0x7f5c0c9d3790>, '__classcell__': <cell at 0x7f5c2621d1c0: SingletonMetaMeta object at 0x55ecc6137fb0>}), kwargs: {}
class created
SingletonMeta __call__, args:(1,), kwargs: {}
Singleton __new__, args:(), kwargs: {}
Traceback (most recent call last):
  File "/home/noam.s/src/uv_metadata/uv_metadata/utils/asd.py", line 41, in <module>
    instance = Singleton(1)
  File "/home/noam.s/src/uv_metadata/uv_metadata/utils/asd.py", line 16, in __call__
    instance = cls.__new__(*args, **kwargs)
  File "/home/noam.s/src/uv_metadata/uv_metadata/utils/asd.py", line 24, in __new__
    self = super().__new__(cls)
TypeError: super(type, obj): obj must be an instance or subtype of type

Trying to replace cls.__instance = super(SingletonMetaMeta, cls).__call__(*args, **kwargs)

with

            instance = super(SingletonMetaMeta, cls).__new__(*args, **kwargs)
            instance.__init__(*args, **kwargs)

gives

SingletonMeta __new__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f7aefc875e0>, '__init__': <function Singleton.__init__ at 0x7f7aefc87670>, '__call__': <function Singleton.__call__ at 0x7f7aefc87700>, 'foo': <function Singleton.foo at 0x7f7aefc87790>, '__classcell__': <cell at 0x7f7b094d01c0: empty>}), kwargs: {}
SingletonMetaMeta __init__, args:('Singleton', (), {'__module__': '__main__', '__qualname__': 'Singleton', '__new__': <function Singleton.__new__ at 0x7f7aefc875e0>, '__init__': <function Singleton.__init__ at 0x7f7aefc87670>, '__call__': <function Singleton.__call__ at 0x7f7aefc87700>, 'foo': <function Singleton.foo at 0x7f7aefc87790>, '__classcell__': <cell at 0x7f7b094d01c0: SingletonMetaMeta object at 0x55b942d98fb0>}), kwargs: {}
class created
SingletonMeta __call__, args:(1,), kwargs: {}
Traceback (most recent call last):
  File "/home/noam.s/src/uv_metadata/uv_metadata/utils/asd.py", line 41, in <module>
    instance = Singleton(1)
  File "/home/noam.s/src/uv_metadata/uv_metadata/utils/asd.py", line 16, in __call__
    instance = super(SingletonMetaMeta, cls).__new__(*args, **kwargs)
TypeError: type.__new__(X): X is not a type object (int)

What’s the correct way to separate __call__ in this case?

Bonus: Can the same be done for the call of type, thus changing what happens on the line class Singleton(metaclass=SingletonMetaMeta):?

Asked By: Gulzar

||

Answers:

The way to do it is to replace

cls.__instance = super(SingletonMetaMeta, cls).__call__(*args, **kwargs)

with

instance = cls.__new__(cls, *args, **kwargs)
instance.__init__(*args, **kwargs)

The __new__ method of a class is special in that it is an static method, and its first argument, the class which it belongs to, always have to be passed explicitly. __init__ on the other hand is an ordinary method for this purpose, and Python will insert the self argument as usual in the call.

Still: using metaclasses for singletons in Python is way overkill, and an antipattern. Just create a module level instance of a class, and, if needed, delete the class from the namespace will work in most cases.

class Singleton:
    ...

singleton = Singleton
del Singleton

If users of your singleton should try to create a new instance, and just get the same, instead of having a created object like "None", "True", you can write a __call__ method which returns self and name the instance the same as the class instead:

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

Singleton = Singleton()

# no need for `del` here: the class declaration have been 
# superseded by the instance. 
Answered By: jsbueno
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.