【问题标题】:How to create an immutable dictionary in python?如何在python中创建不可变字典?
【发布时间】:2012-06-16 08:59:04
【问题描述】:

我想在python中subclassdict这样子类的所有字典都是不可变的。

我不明白__hash__ 是如何影响不变性的,因为在我的理解中它只是表示对象的平等不平等

那么,__hash__ 可以用来实现不变性吗?怎么样?

更新

目标是来自 API 的公共响应可用作 dict,它必须作为全局变量共享。那么,无论如何,它都需要完好无损?

【问题讨论】:

  • 这已经被问过很多次了(不幸的是,我现在手头没有链接)。对于大多数问题,尝试制作不可变字典是错误的方法。你到底想达到什么目的?
  • 这个链接怎么样:stackoverflow.com/q/2703599/623518
  • 我会覆盖你的dict的__setitem__()方法。但是请注意,这并不能保证您的 dict 的值是不可变的(例如,您的值是列表)。
  • @JoelCornett:您至少还需要覆盖__delitem__()clear()pop()popitem()setdefault()update()
  • @SvenMarnach:哈哈哈我忘了那些 XD。是的,你会的。

标签: python class subclass immutability


【解决方案1】:

那么,__hash__ 可以用来实现不变性吗?

不,它不能。无论其__hash__ 方法做什么,都可以使对象可变(或不可变)。

不可变对象和__hash__之间的关系是,由于不可变对象不能改变,所以__hash__返回的值在构造后保持不变。对于可变对象,情况可能是这样,也可能不是(推荐的做法是此类对象根本无法散列)。

如需进一步讨论,请参阅Issue 13707: Clarify hash() constency period

【讨论】:

  • 嗯,用户定义的类默认是可变的和可散列的,但散列值将保持不变!
  • @SvenMarnach:你说的是真的。我说的也是真的。 TBH,我看不出你的推理方向……
  • 我只是建议您回答的最后一句话有些不完整。不可变性和哈希性之间的关系还有更多。
  • 显然,我需要更具体一些。可能随时间变化的哈希值在 Python 中是毫无用处的。散列的主要目的是可以在集合和字典中使用可散列的对象。如果哈希值发生变化,这将以奇怪且不可预测的方式失败。所以所有合理的散列实现都具有散列值保持不变的特性,没有例外。
  • 能否提供一些示例代码How to write an immutable dict ?
【解决方案2】:

关于哈希性和可变性之间的关系:

为了有用,哈希实现需要满足以下属性:

  1. 使用==比较相等的两个对象的哈希值必须相等。

  2. 哈希值可能不会随时间变化。

这两个属性意味着可散列的类在比较实例时不能考虑可变属性,相反,在比较实例时确实考虑了可变属性的类是不可散列的。不可变类可以在不影响比较的情况下进行哈希处理。

所有内置的可变类型都不是可散列的,所有不可变的内置类型都是可散列的。这主要是上述观察的结果。

默认情况下,用户定义的类定义基于对象标识的比较,并使用id() 作为哈希。它们是可变的,但在比较实例时不考虑可变数据,因此可以将它们设为可哈希。

使一个类可散列并不会以某种神奇的方式使其不可变。相反,要在保留原始比较运算符的同时以合理的方式使字典可散列,您首先需要使其不可变。

编辑:关于您的更新:

有几种方法可以提供等效的全局不可变字典:

  1. 改用collections.namedtuple() 实例。

  2. 使用具有只读属性的用户定义类。

  3. 我通常会这样做:

    _my_global_dict = {"a": 42, "b": 7}
    
    def request_value(key):
        return _my_global_dict[key]
    

    通过前导下划线,您清楚地表明_my_global_dict 是应用程序代码不能触及的实现细节。请注意,如果字典 values 碰巧是可变对象,此代码仍然允许修改它们。如有必要,您可以通过返回值的copy.copy()s 或copy.deepcopy()s 来解决此问题。

【讨论】:

  • 那么,请给出一些可以使它成为immutable的代码?因为这只是回答了一半的问题!
  • @YugalJindle:正如我之前多次说过的,使用不可变字典很可能是解决您的问题的错误解决方案,但我们只能告诉您,如果您告诉我们您的问题实际上是什么。 (此外,我的印象是您对好的解决方案并不真正感兴趣,并且您并没有真正注意答案和 cmets。否则,您已经找到了一些示例代码,这些示例代码链接在 cmets上面。)
  • @YugalJindle:不,这当然不是您的唯一解决方案。同样,该代码适用于在您的问题的第二条评论中链接的不可变字典。
  • 我真的不明白你的代码是如何实现不可变字典的。您为键查询编写了一个包装器方法,但我想将其作为一个适当的 dict 对象提供,该对象可以执行除写操作之外的所有操作。我在问题subclass 中说过。另外,我不能使用 deepcopy,因为它非常慢。
  • @YugalJindle:很抱歉不服从您的命令。我想我不能再帮你了。祝你好运!
【解决方案3】:

我发现了一个官方参考:建议包含在被拒绝的 PEP 中。

class imdict(dict):
    def __hash__(self):
        return id(self)

    def _immutable(self, *args, **kws):
        raise TypeError('object is immutable')

    __setitem__ = _immutable
    __delitem__ = _immutable
    clear       = _immutable
    update      = _immutable
    setdefault  = _immutable
    pop         = _immutable
    popitem     = _immutable

署名:http://www.python.org/dev/peps/pep-0351/

【讨论】:

  • 这个 PEP 被拒绝了
  • 这是一个不好的答案,绝对不是官方的。您不应该从 dict 派生,也没有必要不必要地覆盖这么多方法。
  • 编辑删除了作为“官方参考”的声明
【解决方案4】:

frozendict中,hash是在被拒绝的PEP 416之后简单实现的:

def __hash__(self):
    try:
        fs = frozenset(self.items())
    except TypeError:
        hash = -1
    else:
        hash = hash(fs)
    
    if hash == -1:
        raise TypeError("Not all values are hashable.")
    
    return hash

PS:我是包的新维护者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-10
    • 2019-03-01
    • 2019-08-07
    • 1970-01-01
    • 2014-03-03
    • 2023-03-15
    • 1970-01-01
    • 2015-12-07
    相关资源
    最近更新 更多