我在Python中有一个可变类,我希望能够"冻结",此时它是不可变的,因此可以有__hash__
函数。
我担心的是,拥有__hash__
函数会使Python行为异常,因为它可能会检查是否存在哈希函数。
我意识到我可以使用具有哈希函数的子类,将类复制到子类型。但我有兴趣知道Python是否支持可选哈希函数。
在下面的示例中,它适用于基本情况(但在其他情况下可能会失败)。
注意:这假设您没有直接触摸_var
或_is_frozen
,只使用访问方法。
注意:它可能更Pythonic不使用此方法,而是有一个FrozenMyVar
类,但我很好奇,如果可以认为这是支持在Python中。
class MyVar:
__slots__ = ("_var", "_is_frozen")
def __init__(self, var):
self._var = var
self._is_frozen = False
def freeze(self):
self._is_frozen = True
def __hash__(self):
if not self._is_frozen:
raise TypeError("%r not hashable (freeze first)" % type(self))
return hash(self._var)
def __eq__(self, other):
try:
return self.val == other.val
except:
return NotImplemented
@property
def var(self):
return self._var
@var.setter
def var(self, value):
if self._is_frozen:
raise AttributeError("%r is frozen" % type(self))
self._var = value
# ------------
# Verify Usage
v = MyVar(10)
v.var = 9
try:
hash(v)
except:
print("Hash fails on un-frozen instance")
v.freeze()
try:
v.var = 11
except:
print("Assignment fails on frozen instance")
print("Hash is", hash(v))
添加关于真实世界用例的注释,我们有一些带有Vector / Matrix / Quaternion / Euler类的线性数学模块。在某些情况下,我们希望拥有例如一组"矩阵"或者带有矢量键的" dict"。它总是有可能将它们扩展为元组,但它们会占用更多的内存和数据。他们没有能力表现我们自己的数学类型 - 所以冻结它们的能力很有吸引力。
答案 0 :(得分:3)
最初的例子并没有合理地工作#34;因为班级有__hash__
但没有__eq__
,而https://docs.python.org/3/reference/datamodel.html#object.hash说" ;如果某个类未定义 eq ()方法,则不应定义哈希()操作"。但OP的编辑解决了这个问题。
这样做,如果类及其实例确实与所列出的规则一起使用,则行为应该符合规范:实例是"天生不可能的"但是"变得可以洗#34; - "不可逆转地"如果他们的self.val
被调用,那么当他们的freeze
反过来时,只有他们的collections.Hashable
方法被调用了。
当然__hash__
会错误地归类"解冻实例(因为它只检查>>> import collections
>>> isinstance((1, [2,3], 4), collections.Hashable)
True
>>> hash((1, [2,3], 4))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
的存在,而不是它的实际工作),但这几乎不是唯一的行为:
tuple
__hash__
确实出现&#34; hashable&#34;,就像所有元组一样(因为它的类型定义了hash
) - 但是如果你实际上尝试TypeError
它,你仍然得到一个list
,因为其中一个项目是class MyVar(object):
_is_frozen = False
def __init__(self, var):
self._var = var
def freeze(self):
self.__class__ = FrozenMyVar
def __eq__(self, other):
try:
return self.val == other.val
except:
return NotImplemented
__hash__ = None
@property
def var(self):
return self._var
@var.setter
def var(self, value):
if self._is_frozen:
raise AttributeError("%r is frozen" % type(self))
self._var = value
class FrozenMyVar(MyVar):
_is_frozen = True
def __hash__(self):
return hash(self._var)
(使整个实际上不可用! - )。 OP类的尚未冻结的实例与这样的元组的行为类似。
避免这种小故障(但不需要可能繁重的数据副本)的另一种方法是对&#34;冻结&#34;进行建模。作为实例&#34;就地改变类型&#34;,例如......:
object layout differs
此操作与原始示例基本相似(我已删除&#34;插槽&#34;以避免__class__
分配collections.Hashable
错误时出现问题但可能被视为改进对象模型自&#34;就地改变类型&#34;很好地模拟了这种不可逆转的行为变化(并且作为一个小的副作用__class__
现在表现得无可挑剔: - )。
对象的概念&#34;就地改变类型&#34;因为很少有语言甚至可以容忍它,所以一些人会感到害怕,即使在Python中,对于这种语言的这种模糊特征而言,实际使用案例也是一件罕见的事情。但是,确实存在用例 - 这就是为什么{{1}}赋值确实支持的原因! - )