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

标签 python hash python-3.4

我在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/

相关文章:

python - 使用 np.where 查找二维数组中的匹配行

algorithm - 使用开放链和单独寻址检查成员资格

ruby - Ruby 中的哈希值存储为副本?

python - 向 asyncio Transport 添加方法

ssl - smtplib.SMTP starttls 失败并出现 tlsv1 警报解码错误

python - 具有固定队列大小或缓冲区的 multiprocessing.Pool.imap_unordered?

python - 比较两个数据帧列并输出第三个

python - 如何使 Python 中的 json.dumps 忽略不可序列化的字段

c# - 根据多个数据创建相同的 "random"float

java - 特定的 IDE,用于特定的事物