如何制作一个 class 不允许重复实例(尽可能返回现有实例)?
How to make a class which disallows duplicate instances (returning an existing instance where possible)?
我有数据,每个条目都需要是 class 的一个实例。我期望在我的数据中遇到许多重复的条目。我基本上想得到一组所有唯一条目(即丢弃任何重复条目)。然而,事后实例化整个批次并将它们放入一个集合中并不是最优的,因为...
- 我有很多个条目,
- 重复条目的比例预计会很高,
- 我的
__init__()
方法对每个唯一条目进行了大量昂贵的计算,因此我想避免不必要地重做这些计算。
我知道这基本上是同一个问题 但是...
接受的答案实际上并没有解决问题。如果您将 __new__()
return 设为现有实例,从技术上讲它不会创建新实例,但它仍会调用 __init__()
然后重做您已经完成的所有工作,这使得覆盖 __new__()
完全没有意义。 (这很容易通过在 __new__()
和 __init__()
中插入 print
语句来证明,这样您就可以看到它们何时 运行。)
另一个答案需要调用 class 方法,而不是在需要新实例时调用 class 本身(例如:x = MyClass.make_new()
而不是 x = MyClass()
).这行得通,但恕我直言,这并不理想,因为这不是人们认为创建新实例的正常方式。
是否可以覆盖 __new__()
以便它将 return 现有实体 没有 运行 重新 __init__()
?如果这不可能,是否有其他方法可以解决这个问题?
假设您有识别重复实例的方法以及此类实例的映射,您有几个可行的选择:
使用 classmethod
为您获取实例。 class 方法与您的 metaclass 中的 __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
我建议使用这个基数 class,尤其是如果您的 class 是可变的。您不希望一个实例的修改出现在另一个实例中,而没有明确说明这些实例可能是相同的。做 cls(*args, **kwargs)
意味着你每次都得到一个不同的实例,或者至少你的实例是不可变的,你不在乎。
在你的 metaclass 中重新定义 __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__
相当于为cls
调用__new__
和__init__
。
在这两种情况下,基本缓存代码是相同的。主要区别在于如何从用户的角度获取新实例。使用像 get_instance
这样的 classmethod
可以直观地告知用户他们正在获得一个重复的实例。使用对 class 对象的正常调用意味着该实例将始终是新的,因此应该只对不可变的 classes.
请注意,在上面显示的两种情况下,在没有 __init__
的情况下调用 __new__
没有多大意义。
第三个混合选项是可能的。使用此选项,您将创建一个新实例,但从现有实例复制 [=21=] 计算的昂贵部分,而不是重新做一遍。如果通过 metaclass 实现,此版本不会造成任何问题,因为所有实例实际上都是独立的:
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__
如果需要)。
我有数据,每个条目都需要是 class 的一个实例。我期望在我的数据中遇到许多重复的条目。我基本上想得到一组所有唯一条目(即丢弃任何重复条目)。然而,事后实例化整个批次并将它们放入一个集合中并不是最优的,因为...
- 我有很多个条目,
- 重复条目的比例预计会很高,
- 我的
__init__()
方法对每个唯一条目进行了大量昂贵的计算,因此我想避免不必要地重做这些计算。
我知道这基本上是同一个问题
接受的答案实际上并没有解决问题。如果您将
__new__()
return 设为现有实例,从技术上讲它不会创建新实例,但它仍会调用__init__()
然后重做您已经完成的所有工作,这使得覆盖__new__()
完全没有意义。 (这很容易通过在__new__()
和__init__()
中插入print
语句来证明,这样您就可以看到它们何时 运行。)另一个答案需要调用 class 方法,而不是在需要新实例时调用 class 本身(例如:
x = MyClass.make_new()
而不是x = MyClass()
).这行得通,但恕我直言,这并不理想,因为这不是人们认为创建新实例的正常方式。
是否可以覆盖 __new__()
以便它将 return 现有实体 没有 运行 重新 __init__()
?如果这不可能,是否有其他方法可以解决这个问题?
假设您有识别重复实例的方法以及此类实例的映射,您有几个可行的选择:
使用
之前检查具有所请求密钥的实例是否已经存在classmethod
为您获取实例。 class 方法与您的 metaclass 中的__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
我建议使用这个基数 class,尤其是如果您的 class 是可变的。您不希望一个实例的修改出现在另一个实例中,而没有明确说明这些实例可能是相同的。做
cls(*args, **kwargs)
意味着你每次都得到一个不同的实例,或者至少你的实例是不可变的,你不在乎。在你的 metaclass 中重新定义
__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__
相当于为cls
调用__new__
和__init__
。
在这两种情况下,基本缓存代码是相同的。主要区别在于如何从用户的角度获取新实例。使用像 get_instance
这样的 classmethod
可以直观地告知用户他们正在获得一个重复的实例。使用对 class 对象的正常调用意味着该实例将始终是新的,因此应该只对不可变的 classes.
请注意,在上面显示的两种情况下,在没有 __init__
的情况下调用 __new__
没有多大意义。
第三个混合选项是可能的。使用此选项,您将创建一个新实例,但从现有实例复制 [=21=] 计算的昂贵部分,而不是重新做一遍。如果通过 metaclass 实现,此版本不会造成任何问题,因为所有实例实际上都是独立的:
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__
如果需要)。