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

标签 python class duplicates instance

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

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

我认识到这基本上与提出的问题 here 相同。但是...

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

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

可以__new__()被覆盖,以便它将返回一个现有实体而不运行 __init__()又在上面吗?如果这不可能,是否有其他方法可以解决这个问题?

最佳答案

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

  1. 使用 classmethod 为您获取实例。 classmethod 的用途与 __call__ 类似。在您的元类中(当前为 type )。主要区别在于,它会在调用 __new__ 之前检查具有所请求 key 的实例是否已存在。 :

    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 .

在这两种情况下,基本缓存代码是相同的。主要区别在于如何从用户的角度获取新实例。使用classmethod喜欢 get_instance直观地通知用户他们正在获取重复的实例。使用对类对象的正常调用意味着实例将始终是新的,因此只能对不可变类执行此操作。

请注意,在上面显示的两种情况下,调用 __new__ 都没有多大意义。没有__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__和(super().__new__,如果您需要的话)。

  • 关于python - 如何创建一个不允许重复实例的类(尽可能返回现有实例)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50883923/

    相关文章:

    javascript - 为什么我的页面在提交帖子后没有重定向?

    python - 在 Python 中合并不同的字典

    c# - 使用对象 ID 与将对象填充到其他类之间的权衡是什么?

    actionscript-3 - as3 - 从空变量中找出变量类型

    c++ - 让类成员函数调用类外的函数

    python - 如何删除列表中彼此相邻的重复值之一?

    python - 属性错误 : 'DataFrame' object has no attribute 'map'

    python - django/休息 : Can I have serializer with only one field?

    php - 如何复制MySQL字段?

    regex - 在 Notepad++ 中查找重复单词