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

时间:2018-06-15 23:54:39

标签: python class duplicates instance

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

  1. 我有许多条目,
  2. 预计重复参赛作品的比例会相当高,
  3. 我的__init__()方法为每个唯一条目执行了大量昂贵的计算,因此我希望避免不必要地重做这些计算。
  4. 我认识到这与here提出的问题基本相同,但是......

    1. 接受的答案并没有真正解决问题。如果您让__new__()返回现有实例,它在技术上不会创建新实例,但它仍然会调用__init__(),然后重新执行您已经完成的所有工作,这会使压倒__new__()完全没有意义。 (通过在print__new__()中插入__init__()语句可以很容易地证明这一点,这样您就可以看到它们何时运行。)

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

    3. 是否可以覆盖__new__(),以便它会返回现有实体再次运行__init__()?如果这是不可能的,还有其他方法可以解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

假设您有一种方法可以识别重复的实例,并可以对此类实例进行映射,那么您可以使用一些可行的选择:

  1. 使用classmethod为您获取实例。该类方法的作用与您的元类(当前为withNavigation)中的__call__类似。主要区别在于,它将在调用type之前检查具有请求的键的实例是否已经存在:

    __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 意味着您每次都会得到一个不同的实例,或者至少您的实例是不可变的,并且您不在乎。

  2. 在您的元类中重新定义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之类的classmethod可以直观地告知用户他们正在获取重复的实例。使用对类对象的常规调用意味着该实例将始终是新的,因此仅应对不可变的类进行此操作。

请注意,在上述两种情况下,调用get_instance而不使用__new__都没有多大意义。

  1. 第三个混合选项是可行的。使用此选项,您将创建一个新实例,但是要从现有实例复制__init__的昂贵计算部分,而不是重新进行一次。如果通过元类实现,此版本不会引起任何问题,因为所有实例实际上都是独立的:

    __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__)。