Reputation: 28074
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.
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):
?
Upvotes: 1
Views: 183
Reputation: 110726
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.
Upvotes: 1