如何创建一个不允许重复实例的类(尽可能返回现有实例)?



我有数据,每个条目都需要是一个类的实例。 我希望在我的数据中遇到许多重复的条目。我基本上想得到一组所有唯一条目(即丢弃任何重复项(。 但是,实例化整个批次并在事后将它们放入一个集合中并不是最佳的,因为......

  1. 有很多条目,
  2. 重复条目的比例预计会相当高,
  3. 我的__init__()方法是为每个唯一条目进行大量昂贵的计算,因此我想避免不必要地重做这些计算。

我承认这基本上与这里提出的问题相同,但是......

  1. 接受的答案实际上并不能解决问题。 如果您__new__()返回现有实例,从技术上讲,它不会创建新实例,但它仍然会调用__init__()然后重做您已经完成的所有工作,这使得覆盖__new__()完全没有意义。 (这很容易通过在__new__()__init__()中插入print语句来演示,以便您可以看到它们何时运行。

  2. 另一个答案需要调用类方法,而不是在你想要一个新实例时调用类本身(例如:x = MyClass.make_new()而不是x = MyClass()(。 这有效,但恕我直言,这不是理想的,因为这不是人们会想到的新实例的正常方式。

是否可以覆盖__new__(),以便它将返回现有实体而不再次运行__init__()? 如果这是不可能的,还有没有另一种方法可以解决这个问题?

假设您有一种方法可以识别重复的实例,并且有此类实例的映射,那么您有几个可行的选择:

  1. 使用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)意味着您每次都会获得不同的实例,或者至少您的实例是不可变的,您不在乎。

  2. 在元类中重新定义__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__都没有多大意义。

  1. 第三种混合选项是可能的。使用此选项,您将创建一个新实例,但从现有实例复制__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__(。

最新更新