我知道这是一个老问题,但是如果只想基于传递给构造函数的参数创建一个类的单个实例,这里有一个非常宝贵的用例。
实例单例 我使用此代码在 Z-Wave 网络上创建设备的单例实例。无论我创建实例多少次,如果将相同的值传递给构造函数,如果存在具有完全相同值的实例,那么这就是返回的内容。
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))
输出
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
现在有人可能会说它可以在不使用元类的情况下完成,我知道如果 __init__ 方法被修饰就可以完成。我不知道另一种方法。下面的代码虽然将返回一个包含所有相同数据的类似实例,但它不是单例实例,但会创建一个新实例。因为它创建了一个具有相同数据的新实例,所以需要采取额外的步骤来检查实例的相等性。最后,它比使用元类消耗更多的内存,并且使用元类不需要采取额外的步骤来检查相等性。
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))
输出
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
如果还需要检查新实例的删除或添加,元类方法非常好用。
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))
输出
number of instances: 2
number of instances: 3
这是一种在实例不再使用后删除已创建的实例的方法。
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))
输出
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
如果您查看输出,您会注意到 Test1 实例的数量没有改变。那是因为 test1 和 test3 是同一个实例,我只删除了 test1 所以代码中仍然有对 test1 实例的引用,因此 test1 实例没有被删除。
另一个不错的特性是,如果实例仅使用提供的参数来完成它的任务,那么您可以使用元类来促进在完全不同的计算机上或在同一台机器上的不同进程中远程创建实例. 参数可以简单地通过套接字或命名管道传递,并且可以在接收端创建类的副本。