如何重用现有对象以实现不可变对象?
在Python中,为什么可以重复使用已经存在的相等的不可变对象(就像对待str
字符串那样)?这是不是只需要定义一个__hash__
方法就可以,还是说需要更复杂的操作?
3 个回答
我觉得你需要保持一个字典 {args: object},用来存储已经创建的实例。然后,你需要重写这个类的 __new__
方法,去检查这个字典,如果对象已经存在,就返回那个对象。需要注意的是,我还没有实现或测试这个想法。当然,字符串是在C语言层面处理的。
这里有两种“软件工程”解决方案,不需要深入了解Python的底层知识。它们适用于以下情况:
第一种情况:如果你的类的对象是用相同的构造参数创建的,那么这些对象就是“相等”的,并且这种相等关系在创建后不会改变。解决方案:使用一个工厂来对构造参数进行哈希处理:
class MyClass:
def __init__(self, someint, someotherint):
self.a = someint
self.b = someotherint
cachedict = { }
def construct_myobject(someint, someotherint):
if (someint, someotherint) not in cachedict:
cachedict[(someint, someotherint)] = MyClass(someint, someotherint)
return cachedict[(someint, someotherint)]
这种方法基本上限制了你的类的实例,每一对不同的输入只能对应一个独特的对象。不过,这种方法也有明显的缺点:并不是所有类型都容易进行哈希处理等等。
第二种情况:你的类的对象是可变的,它们的“相等性”可能会随着时间的推移而改变。解决方案:定义一个类级别的相等实例注册表:
class MyClass:
registry = { }
def __init__(self, someint, someotherint, third):
MyClass.registry[id(self)] = (someint, someotherint)
self.someint = someint
self.someotherint = someotherint
self.third = third
def __eq__(self, other):
return MyClass.registry[id(self)] == MyClass.registry[id(other)]
def update(self, someint, someotherint):
MyClass.registry[id(self)] = (someint, someotherint)
在这个例子中,具有相同的 someint, someotherint
组合的对象是相等的,而第三个参数则不影响相等性。关键是要保持 registry
中的参数同步。作为 update
的替代方案,你也可以重写你的类中的 getattr
和 setattr
;这样可以确保任何赋值操作 foo.someint = y
都会与类级别的字典保持同步。可以在这里查看一个示例 这里。
如果你想通过类的构造函数来创建对象,并且希望它返回一个已经创建过的对象,那么你需要提供一个 __new__
方法(因为当你到达 __init__
时,对象已经被创建了)。
这里有一个简单的例子 - 如果用来初始化的值之前已经出现过,那么就会返回一个已经创建的对象,而不是新创建一个:
class Cached(object):
"""Simple example of immutable object reuse."""
def __init__(self, i):
self.i = i
def __new__(cls, i, _cache={}):
try:
return _cache[i]
except KeyError:
# you must call __new__ on the base class
x = super(Cached, cls).__new__(cls)
x.__init__(i)
_cache[i] = x
return x
请注意,在这个例子中,你可以使用任何可以哈希的值来初始化。为了证明对象确实被重用了:
>>> a = Cached(100)
>>> b = Cached(200)
>>> c = Cached(100)
>>> a is b
False
>>> a is c
True