代码之家  ›  专栏  ›  技术社区  ›  Azat Ibrakov

在dict/set中查找不可损坏对象时出现“TypeError:unhashable type”

  •  0
  • Azat Ibrakov  · 技术社区  · 6 年前

    前言

    我明白 dict s/ set 由于实现的原因,应该使用散列对象来创建/更新s,因此当这类代码失败时

    >>> {{}}  # empty dict of empty dict
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    TypeError: unhashable type: 'dict'
    

    没关系,我见过很多这样的信息。

    但是如果我想检查是否有不易损坏的东西在里面 设置 口述

    >>> {} in {}  # empty dict not in empty dict
    

    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    TypeError: unhashable type: 'dict'
    

    问题

    这种行为背后的理由是什么?我知道查找和更新可能在逻辑上是相互关联的(如 dict.setdefault method ),但它不应该在修改步骤而不是查找时失败吗?也许我有一些散列的“特殊”值,我以某种方式处理,但其他一些值(可能不易损坏)--以另一种方式:

    SPECIAL_CASES = frozenset(range(10)) | frozenset(range(100, 200))
    ...
    def process_json(obj):
        if obj in SPECIAL_CASES:
            ...  # handle special cases
        else:
            ...  # do something else
    

    所以对于给定的查找行为,我不得不使用其中一个选项

    • LBYL 方法:检查 obj 是可散列的,并且只有在检查之后,如果它是 SPECIAL_CASES (这不是很好,因为它是基于 特殊情况 结构和查找机制限制,但可以封装在单独的谓词中),
    • EAFP 方法:使用某种工具进行“安全查找”,例如

      def safe_contains(dict_or_set, obj):
          try:
              return obj in dict_or_set
          except TypeError:
              return False
      
    • 使用 list / tuple 对于 特殊情况 (事实并非如此 O(1)

    还是我错过了一些琐碎的事情?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Aaron    6 年前

    毫无疑问,你已经意识到,集合和字典在它们的内部工作方式上非常相似。基本上,这个概念是你有键值对(或者只有一个集合的键),而且这个键永远不能改变(不可变)。如果一个对象是可变的,散列将失去它作为底层数据的唯一标识符的意义。如果你不能判断一个对象是否唯一,那么一组唯一键的含义就失去了它唯一性的键属性。这就是为什么在集合中不允许可变类型作为dict的键 {} in {} # empty dict not in empty dict 我觉得你有点误会 dict.__contains__ 只检查dict的键,不检查值。因为您永远不能将dict作为键(因为它是可变的),所以这是无效的。

        2
  •  1
  •   Azat Ibrakov    6 年前

    我发现了 this issue 在Python bug tracker上。长话短说:

    如果

    >>> set([1,2]) in {frozenset([1,2]): 'a'}
    

    返回 False 这在某种程度上是违反直觉的,因为值是相等的

    >>> set([1,2]) == frozenset([1,2])
    True
    

    因此,我想在可能发生这种情况的情况下编写并使用适当的实用程序。


    关于错误的根源 CPython repo dict___contains__ function (这是一个 dict.__contains__ 方法实现)调用 PyObject_Hash function (对应于 hash function )->对于不易损坏的对象(如 {} 在我们的第一个案例)电话 PyObject_HashNotImplemented function ->生成此错误。