支持__hash__的Python类(取决于实例)

时间:2015-02-14 15:47:51

标签: python hash python-3.4

我在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"。它总是有可能将它们扩展为元组,但它们会占用更多的内存和数据。他们没有能力表现我们自己的数学类型 - 所以冻结它们的能力很有吸引力。

1 个答案:

答案 0 :(得分:3)

最初的例子并没有合理地工作#34;因为班级有__hash__但没有__eq__,而https://docs.python.org/3/reference/datamodel.html#object.hash说" ;如果某个类未定义 eq ()方法,则不应定义哈希()操作"。但OP的编辑解决了这个问题。

这样做,如果类及其实例确实与所列出的规则一起使用,则行为应该符合规范:实例是"天生不可能的"但是"变得可以洗#34; - "不可逆转地"如果他们的self.val被调用,那么当他们的freeze反过来时,只有他们的collections.Hashable方法被调用了。

当然__hash__会错误地归类&#34;解冻实例(因为它只检查>>> 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}}赋值确实支持的原因! - )