我有数据,每个条目都需要是一个类的实例。 我希望在我的数据中遇到许多重复的条目。我基本上想得到一组所有唯一条目(即丢弃任何重复项(。 但是,实例化整个批次并在事后将它们放入一个集合中并不是最佳的,因为......
- 我有很多条目,
- 重复条目的比例预计会相当高,
- 我的
__init__()
方法是为每个唯一条目进行大量昂贵的计算,因此我想避免不必要地重做这些计算。
我承认这基本上与这里提出的问题相同,但是......
-
接受的答案实际上并不能解决问题。 如果您
__new__()
返回现有实例,从技术上讲,它不会创建新实例,但它仍然会调用__init__()
然后重做您已经完成的所有工作,这使得覆盖__new__()
完全没有意义。 (这很容易通过在__new__()
和__init__()
中插入print
语句来演示,以便您可以看到它们何时运行。 -
另一个答案需要调用类方法,而不是在你想要一个新实例时调用类本身(例如:
x = MyClass.make_new()
而不是x = MyClass()
(。 这有效,但恕我直言,这不是理想的,因为这不是人们会想到的新实例的正常方式。
是否可以覆盖__new__()
,以便它将返回现有实体而不再次运行__init__()
? 如果这是不可能的,还有没有另一种方法可以解决这个问题?
假设您有一种方法可以识别重复的实例,并且有此类实例的映射,那么您有几个可行的选择:
-
使用
classmethod
为您获取实例。类方法的目的与元类(当前type
(中的__call__
类似。主要区别在于,它会在调用__new__
之前检查具有请求密钥的实例是否已存在:class QuasiSingleton: @classmethod def make_key(cls, *args, **kwargs): # Creates a hashable instance key from initialization parameters @classmethod def get_instance(cls, *args, **kwargs): key = cls.make_key(*args, **kwargs) if not hasattr(cls, 'instances'): cls.instances = {} if key in cls.instances: return cls.instances[key] # Only call __init__ as a last resort inst = cls(*args, **kwargs) cls.instances[key] = inst return inst
我建议使用此基类,特别是如果您的类以任何方式可变。您不希望对一个实例的修改显示在另一个实例中,而不明确说明这些实例可能相同。这样做
cls(*args, **kwargs)
意味着您每次都会获得不同的实例,或者至少您的实例是不可变的,您不在乎。 -
在元类中重新定义
__call__
:class QuasiSingletonMeta(type): def make_key(cls, *args, **kwargs): ... def __call__(cls, *args, **kwargs): key = cls.make_key(*args, **kwargs) if not hasattr(cls, 'instances'): cls.instances = {} if key in cls.instances: return cls.instances[key] inst = super().__call__(*args, **kwargs) cls.instances[key] = inst return inst
在这里,
super().__call__
相当于调用__new__
并__init__
cls
。
在这两种情况下,基本缓存代码是相同的。主要区别在于如何从用户的角度获取新实例。使用像get_instance
这样的classmethod
直观地通知用户他们正在获取重复的实例。使用对类对象的正常调用意味着实例将始终是新的,因此应仅对不可变类执行。
请注意,在上面显示的两种情况下,在没有__init__
的情况下调用__new__
都没有多大意义。
-
第三种混合选项是可能的。使用此选项,您将创建一个新实例,但从现有实例复制
__init__
计算的昂贵部分,而不是重新执行。如果通过元类实现,此版本不会引起任何问题,因为所有实例实际上都是独立的:class QuasiSingleton: @classmethod def make_key(cls, *args, **kwargs): ... def __new__(cls, *args, **kwargs): if 'cache' not in cls.__dict__: cls.cache = {} return super().__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs): key = self.make_key(*args, **kwargs) if key in self.cache: # Or more accurately type(self).instances data = self.cache[key] else: data = # Do lengthy computation # Initialize self with data object
使用此选项,请记住调用
super().__init__
和(如果需要,请super().__new__
(。