我有数据,每个条目都需要是一个类的实例。我期待在我的数据中遇到许多重复的条目。我基本上想要得到一组所有唯一条目(即丢弃任何重复项)。然而,实例化整个批次并在事实之后将它们放入集合中是不理想的,因为......
__init__()
方法为每个唯一条目执行了大量昂贵的计算,因此我希望避免不必要地重做这些计算。我认识到这与here提出的问题基本相同,但是......
接受的答案并没有真正解决问题。如果您让__new__()
返回现有实例,它在技术上不会创建新实例,但它仍然会调用__init__()
,然后重新执行您已经完成的所有工作,这会使压倒__new__()
完全没有意义。 (通过在print
和__new__()
中插入__init__()
语句可以很容易地证明这一点,这样您就可以看到它们何时运行。)
另一个答案需要调用类方法,而不是在需要新实例时调用类本身(例如:x = MyClass.make_new()
而不是x = MyClass()
)。这是有效的,但它不是理想的恕我直言,因为它不是人们想到制作新实例的正常方式。
是否可以覆盖__new__()
,以便它会返回现有实体而再次运行__init__()
?如果这是不可能的,还有其他方法可以解决这个问题吗?
答案 0 :(得分: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
意味着您每次都会得到一个不同的实例,或者至少您的实例是不可变的,并且您不在乎。
在您的元类中重新定义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__
)。