我有数据,每个条目都需要是类的实例。我预计我的数据中会遇到许多重复的条目。我本质上希望得到一组所有唯一条目(即丢弃任何重复项)。然而,实例化整个批处理并在事后将它们放入集合中并不是最佳选择,因为......
- 我有很多个条目,
- 重复条目的比例预计会相当高,
- 我的
__init__()
方法对每个唯一条目进行大量昂贵的计算,因此我想避免不必要地重做这些计算。
我认识到这基本上与提出的问题 here 相同。但是...
接受的答案实际上并不能解决问题。如果你做
__new__()
返回一个现有实例,从技术上讲它并没有创建一个新实例,但它仍然调用__init__()
然后重做您已经完成的所有工作,这使得覆盖__new__()
完全没有意义。 (这可以通过在print
和__new__()
中插入__init__()
语句来轻松演示,这样您就可以看到它们何时运行。)另一个答案需要在需要新实例时调用类方法而不是调用类本身(例如:
x = MyClass.make_new()
而不是x = MyClass()
)。这是可行的,但恕我直言,它并不理想,因为这不是人们认为创建新实例的正常方式。
可以__new__()
被覆盖,以便它将返回一个现有实体而不运行 __init__()
又在上面吗?如果这不可能,是否有其他方法可以解决这个问题?
最佳答案
假设您有一种方法来识别重复实例以及此类实例的映射,那么您有一些可行的选择:
使用
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)
意味着您每次都会获得不同的实例,或者至少您的实例是不可变的并且您不关心。重新定义
__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/