我在Python中有一个可变类,我希望能够“卡住”它,此时它是不可变的,因此可以有一个__hash__
函数。
我担心的是,存在 __hash__
函数会让 Python 表现奇怪,因为它可能会检查哈希函数是否存在。
我意识到我可以使用具有哈希函数的子类,将类复制到子类型。但我很想知道 Python 是否支持可选哈希函数。
在下面的示例中,它在基本情况下有效(但在其他情况下可能会失败)。
注意:假设您不直接接触 _var
或 _is_frozen
并且仅使用访问方法。
注意:不使用此方法并使用 FrozenMyVar
类可能更符合 Python 风格,但我很好奇这是否可以被视为在 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))
添加关于现实世界用例的注释,我们有一些带有向量/矩阵/四元数/欧拉类的线性数学模块。在某些情况下,我们希望拥有“一组矩阵”或“带有向量键的字典”。总是可以将它们扩展为元组,但它们会占用更多内存并失去表现我们自己的数学类型的能力 - 因此卡住它们的能力很有吸引力。
最佳答案
原来的例子并没有“合理地”工作,因为该类有 __hash__
但不是__eq__
,如 https://docs.python.org/3/reference/datamodel.html#object.hash说“如果一个类没有定义 eq() 方法,它也不应该定义 hash() 操作”。但OP的编辑解决了这个附带问题。
完成后,如果类及其实例确实按照概述的规则使用,则行为应符合规范:实例“天生不可散列”但“变得可散列”——鉴于所述规则“不可逆转”,并且仅,当然,如果他们的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__
)——但如果您实际上尝试 hash
编译它,你仍然会得到 TypeError
,因为其中一项是 list
(使得整体实际上不可散列!-)。 OP 类的尚未卡住的实例的行为与这样的元组类似。
确实避免这种小故障(但不需要潜在繁重的数据副本)的另一种方法是将“卡住”建模为“就地更改类型”实例,例如...:
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)
这基本上与原始示例类似(我删除了“槽”以避免 object layout differs
分配上的 __class__
错误问题),但可能被认为是自“就地更改类型”模型以来的改进对象模型好吧,这种不可逆转的行为变化(作为一个小副作用collections.Hashable
现在表现得无可挑剔:-)。
对象“就地改变类型”的概念让一些人感到害怕,因为很少有语言确实能够容忍它,即使在 Python 中,当然,对于对象的如此模糊的特性有一个实际的用例也是很少见的。语言。然而,用例确实存在——这就是为什么 __class__
确实支持赋值!-)
关于具有 __hash__ 支持的 Python 类(取决于实例),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28517290/