I am creating a metaclass which ensures that instances of an actual class A
are kind of singletons. But rather than having only single instance of A
, I would like to have one instance per argument set given to A
.
That is
class A(metaclass=ArgumentSingleton):
pass
r = A()
s = A()
t = A(1)
u = A(1)
v = A('hi')
print(r is s) # True
print(r is t) # False
print(t is u) # True
print(v is u) # False
print(v is r) # False
Moreover, the property of 'being a singleton' must survive pickling.
That is
a = A()
with open('tmp.pkl', 'wb') as f:
pkl.dump(a, f)
with open('tmp.pkl', 'rb') as f:
b = pkl.load(f)
print(a is b) # True
For example Sympy is capable of doing that with their constants (such as pi
) and I tried similar approach.
class ArgumentSingleton(type):
_instances = {}
def __new__(cls, name, bases, class_dict):
def __init__(self, *args):
self._args = args
print('Here in __init__')
def __reduce_ex__(self, protocol):
return type(self), (*self._args,)
init = '__init__'
reduce = '__reduce_ex__'
method_absence = [
init not in class_dict,
reduce not in class_dict
]
if all(method_absence):
class_dict[init] = __init__
class_dict[reduce] = __reduce_ex__
elif any(method_absence):
raise ArgumentSingletonException(
f"Either both methods '{init}', '{reduce}' are defined or "
f"none of them in class: {name}"
)
new_class = super().__new__(cls, name, bases, class_dict)
return new_class
def __call__(cls, *args, **kwargs):
key = (cls, *args, *kwargs.items())
if key not in ArgumentSingleton._instances:
ArgumentSingleton._instances[key] = super().__call__(*args, **kwargs)
return cls._instances[key]
The code depends quite deeply (at least to my standards) on inner workings of Python and I am afraid of any hidden bugs and hidden problems.
The purpose of controling presence of __init__
and __reduce_ex__
is to explicitly move responsibility for __reduce_ex__
to the creators of the class A
in case they decide to provide their __init__
.
Any comments or suggestions appreciated!
So far, I am aware of the fact that the arguments must be hashable. That is not problem for me, as this construction is meant to speed up comparison of my complex hashable objects.
b
that is not defined! For the sake of clarity and correctness you might want to add it there :) \$\endgroup\$