I am trying to instantiate a singleton with constructor argument. However, passing the input arguments to the metaclass hits an error.
from typing_extensions import List, TypedDict, Optional, Any
class Singleton(type): # Inherit from "type" in order to gain access to method __call__
_var: str = None
_myObject: MyType = None
def __init__(self, *args, **kwargs):
print(f"Singleton::__init__")
print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
print(**kwargs)
for i in args:
print(f"i: {i}")
if "var" in kwargs and kwargs.get("var"):
self._var = kwargs.get('var')
elif len(args):
self._var = args[0]
else:
self._var = "text-embedding-005"
print(f"Singleton::__init__self._var: {self._var}")
self.__instance = None # Create a variable to store the object reference
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
# if the object has not already been created
self._myObject = MyType(self._var)
print(f"Singleton::__call__ self._var: {self._var}")
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
# if object (Spam) reference already exists; return it
return self.__instance
class MySingleton(metaclass=Singleton):
"""
Class constructor
"""
def __init__(self, *args, **kwargs):
print(f"MySingleton::__init__")
super().__init__(*args, **kwargs)
print(f"MySingleton::__init__ {self._var}")
myobject = MySingleton("hello")
myobject1 = MySingleton("world")
Error:
Singleton::__init__
Singleton::__init__ len(kwargs): 0, len(args): 3, args[0]: MySingleton
i: MySingleton
i: ()
i: {'__module__': '__main__', '__qualname__': 'MySingleton', '__doc__': '\n Class constructor\n', '__init__': <function MySingleton.__init__ at 0x7de2ca4fd800>, '__classcell__': <cell at 0x7de2ca5399f0: Singleton object at 0x12d21760>}
Singleton::__init__self._model: MySingleton
Singleton::__call__ _model: MySingleton
MySingleton::__init__
Traceback (most recent call last):
File "singleton.py", line 43, in <module>
vector_store = MySingleton("hello")
^^^^^^^^^^^^^^^^^^^^
File "singleton.py", line 26, in __call__
self.__instance = super().__call__(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "singleton.py", line 38, in __init__
super().__init__(*args, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
However, even if this doesn't hit any error, I doubt the logic would work. The console output shows that both the metaclass __init__
and __call__
are called first before the MySingleton
constructor. The intput parameter is used to instantiate the ultimate singleton self._myObject
in __call__
.
I am trying to instantiate a singleton with constructor argument. However, passing the input arguments to the metaclass hits an error.
from typing_extensions import List, TypedDict, Optional, Any
class Singleton(type): # Inherit from "type" in order to gain access to method __call__
_var: str = None
_myObject: MyType = None
def __init__(self, *args, **kwargs):
print(f"Singleton::__init__")
print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
print(**kwargs)
for i in args:
print(f"i: {i}")
if "var" in kwargs and kwargs.get("var"):
self._var = kwargs.get('var')
elif len(args):
self._var = args[0]
else:
self._var = "text-embedding-005"
print(f"Singleton::__init__self._var: {self._var}")
self.__instance = None # Create a variable to store the object reference
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
# if the object has not already been created
self._myObject = MyType(self._var)
print(f"Singleton::__call__ self._var: {self._var}")
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
# if object (Spam) reference already exists; return it
return self.__instance
class MySingleton(metaclass=Singleton):
"""
Class constructor
"""
def __init__(self, *args, **kwargs):
print(f"MySingleton::__init__")
super().__init__(*args, **kwargs)
print(f"MySingleton::__init__ {self._var}")
myobject = MySingleton("hello")
myobject1 = MySingleton("world")
Error:
Singleton::__init__
Singleton::__init__ len(kwargs): 0, len(args): 3, args[0]: MySingleton
i: MySingleton
i: ()
i: {'__module__': '__main__', '__qualname__': 'MySingleton', '__doc__': '\n Class constructor\n', '__init__': <function MySingleton.__init__ at 0x7de2ca4fd800>, '__classcell__': <cell at 0x7de2ca5399f0: Singleton object at 0x12d21760>}
Singleton::__init__self._model: MySingleton
Singleton::__call__ _model: MySingleton
MySingleton::__init__
Traceback (most recent call last):
File "singleton.py", line 43, in <module>
vector_store = MySingleton("hello")
^^^^^^^^^^^^^^^^^^^^
File "singleton.py", line 26, in __call__
self.__instance = super().__call__(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "singleton.py", line 38, in __init__
super().__init__(*args, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
However, even if this doesn't hit any error, I doubt the logic would work. The console output shows that both the metaclass __init__
and __call__
are called first before the MySingleton
constructor. The intput parameter is used to instantiate the ultimate singleton self._myObject
in __call__
.
2 Answers
Reset to default 1For much that this is interesting, I should advise you against such mechanisms: Python is not constrained in that all objects must be classes, and the "global variable" mechanism is actually module name-spaced. That means you can just create an ordinary class, create your single instance of it as an importable name, and always import that instance instead of importing the class:
class _MySingletonClass:
def __init__(...):
...
# import this instead:
MySingleton = _MySingletonClass()
# you can even delete the class to avoid
# accidental instantiation:
del _MySingletonClass
And what if your users intend to instantiate it after importing?
(Although no one tries to instantiate None
or True
) but
you can just add a __call__
method to the class which
will allow for a fake instantiation:
class _MySingletonClass:
def __init__(...):
...
def __call__(self):
return self
WIth that said, even if one is to resort to actually using a metaclass to create a singleton class, there is no need to fiddle with __init__
in the metaclass - the __call__
method in the metaclass is what runs for each instance:
class MetaSingleton():
_registry = {}
def __call__(cls, *args, **kwargs):
registry = type(cls)._registry
if cls not in registry:
registry[cls] = super().__call__(*args, **kwargs)
return registry[cls]
As you can see, it can be one order of magnitude simpler than what you are doing.
Even the usage of Python name mangling mechanisms, with __
prefix can complicate things up in unexpected ways - try to avoid it unless really needed - that is not for creating arbitrary "hidden" attributes, it is something intended to avoid name clashes in complex inheritance hierarchies.
You could even add a mechanism to error out if one try to re-instantiate the class with different arguments than on the first call:
class MetaSingleton():
_registry = {}
def __call__(cls, *args, **kwargs):
registry = type(cls)._registry
if cls not in registry:
registry[cls] = (super().__call__(*args, **kwargs), args, kwargs)
elif registry(cls)[1] != args or registry(cls)[2] != kwargs:
raise TypeError(f"Class already initialized with different arguments!")
return registry[cls][0]
(but seriously - just create an instance and use it, as in my first example. Using metaclasses for this is way overkill)
Solution is to put all the logic in subclass:
from typing_extensions import List, TypedDict, Optional, Any
class Singleton(type): # Inherit from "type" in order to gain access to method __call__
def __init__(self, *args, **kwargs):
print(f"Singleton::__init__")
print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
self.__instance = None # Create a variable to store the object reference
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
# if the object has not already been created
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
# if object (Spam) reference already exists; return it
return self.__instance
class MySingleton(metaclass=Singleton):
"""
Class constructor
"""
_var: str = None
def __init__(self, var="Hello"):
print(f"MySingleton::__init__")
self._var = var
self._myObject = MyType(self._var)
print(f"MySingleton::__init__ {self._var}")
myobject = MySingleton("hello")
myobject1 = MySingleton("world")
assert myobject is not None
assert myobject1 is not None
assert myobject == myobject1