I know this is an old question But here is a use case that is really invaluable if wanting to create only a single instance of a class based on the parameters passed to the constructor.
Instance singletons
I use this code for creating a singleton instance of a device on a Z-Wave network. No matter how many times I create an instance if the same values are passed to the constructor if an instance with the exact same values exists then that is what gets returned.
import inspect
class SingletonMeta(type):
# only here to make IDE happy
_instances = {}
def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)
cls._instances = {}
def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}
for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)
if key not in cls._instances:
cls._instances[key] = (
super(SingletonMeta, cls).__call__(*args, **kwargs)
)
return cls._instances[key]
class Test1(metaclass=SingletonMeta):
def __init__(self, param1, param2='test'):
pass
class Test2(metaclass=SingletonMeta):
def __init__(self, param3='test1', param4='test2'):
pass
test1 = Test1('test1')
test2 = Test1('test1', 'test2')
test3 = Test1('test1', 'test')
test4 = Test2()
test5 = Test2(param4='test1')
test6 = Test2('test2', 'test1')
test7 = Test2('test1')
print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)
print('test4 == test2:', test4 == test2)
print('test7 == test3:', test7 == test3)
print('test6 == test4:', test6 == test4)
print('test7 == test4:', test7 == test4)
print('test5 == test6:', test5 == test6)
print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))
output
test1 == test2: False
test2 == test3: False
test1 == test3: True
test4 == test2: False
test7 == test3: False
test6 == test4: False
test7 == test4: True
test5 == test6: False
number of Test1 instances: 2
number of Test2 instances: 3
Now someone might say it can be done without the use of a metaclass and I know it can be done if the __init__ method is decorated. I do not know of another way to do it. The code below while it will return a similiar instance that contains all of the same data it is not a singleton instance, a new instance gets created. Because it creates a new instance with the same data there wuld need to be additional steps taken to check equality of instances. I n the end it consumes more memory then using a metaclass and with the meta class no additional steps need to be taken to check equality.
class Singleton(object):
_instances = {}
def __init__(self, param1, param2='test'):
key = (param1, param2)
if key in self._instances:
self.__dict__.update(self._instances[key].__dict__)
else:
self.param1 = param1
self.param2 = param2
self._instances[key] = self
test1 = Singleton('test1', 'test2')
test2 = Singleton('test')
test3 = Singleton('test', 'test')
print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)
print('test1 params', test1.param1, test1.param2)
print('test2 params', test2.param1, test2.param2)
print('test3 params', test3.param1, test3.param2)
print('number of Singleton instances:', len(Singleton._instances))
output
test1 == test2: False
test2 == test3: False
test1 == test3: False
test1 params test1 test2
test2 params test test
test3 params test test
number of Singleton instances: 2
The metaclass approach is really nice to use if needing to check for the removal or addition of a new instance as well.
import inspect
class SingletonMeta(type):
# only here to make IDE happy
_instances = {}
def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)
cls._instances = {}
def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}
for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)
if key not in cls._instances:
cls._instances[key] = (
super(SingletonMeta, cls).__call__(*args, **kwargs)
)
return cls._instances[key]
class Test(metaclass=SingletonMeta):
def __init__(self, param1, param2='test'):
pass
instances = []
instances.append(Test('test1', 'test2'))
instances.append(Test('test1', 'test'))
print('number of instances:', len(instances))
instance = Test('test2', 'test3')
if instance not in instances:
instances.append(instance)
instance = Test('test1', 'test2')
if instance not in instances:
instances.append(instance)
print('number of instances:', len(instances))
output
number of instances: 2
number of instances: 3
Here is a way to remove an instance that has been created after the instance is no longer in use.
import inspect
import weakref
class SingletonMeta(type):
# only here to make IDE happy
_instances = {}
def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)
def remove_instance(c, ref):
for k, v in list(c._instances.items())[:]:
if v == ref:
del cls._instances[k]
break
cls.remove_instance = classmethod(remove_instance)
cls._instances = {}
def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}
for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)
if key not in cls._instances:
instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
cls._instances[key] = weakref.ref(
instance,
instance.remove_instance
)
return cls._instances[key]()
class Test1(metaclass=SingletonMeta):
def __init__(self, param1, param2='test'):
pass
class Test2(metaclass=SingletonMeta):
def __init__(self, param3='test1', param4='test2'):
pass
test1 = Test1('test1')
test2 = Test1('test1', 'test2')
test3 = Test1('test1', 'test')
test4 = Test2()
test5 = Test2(param4='test1')
test6 = Test2('test2', 'test1')
test7 = Test2('test1')
print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)
print('test4 == test2:', test4 == test2)
print('test7 == test3:', test7 == test3)
print('test6 == test4:', test6 == test4)
print('test7 == test4:', test7 == test4)
print('test5 == test6:', test5 == test6)
print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))
print()
del test1
del test5
del test6
print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))
output
test1 == test2: False
test2 == test3: False
test1 == test3: True
test4 == test2: False
test7 == test3: False
test6 == test4: False
test7 == test4: True
test5 == test6: False
number of Test1 instances: 2
number of Test2 instances: 3
number of Test1 instances: 2
number of Test2 instances: 1
if you look at the output you will notice that the number of Test1 instances has not changed. That is because test1 and test3 are the same instance and I only deleted test1 so there is still a reference to the test1 instance in the code and as a result of that the test1 instance does not get removed.
Another nice feature of this is if the instance uses only the supplied parameters to do whatever it is tasked to do then you can use the metaclass to facilitate remote creations of the instance either on a different computer entirely or in a different process on the same machine. the parameters can simply be passed over a socket or a named pipe and a replica of the class can be created on the receiving end.