【问题标题】:"TypeError: unhashable type" during lookup of unhashable object in dict/set在 dict/set 中查找不可散列对象期间的“TypeError:不可散列类型”
【发布时间】:2019-02-23 01:04:15
【问题描述】:

前言

我知道dicts/sets 应该仅因为它们的实现而使用可散列对象创建/更新,所以当这种代码失败时

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

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

但是如果我想检查一些不可散列的对象是否在set/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 之一(这不是很好,因为它基于 SPECIAL_CASES 结构和查找机制限制,但可以封装在单独的谓词),
  • EAFP 方式:使用某种实用程序进行“安全查找”,例如

    def safe_contains(dict_or_set, obj):
        try:
            return obj in dict_or_set
        except TypeError:
            return False
    
  • list/tuple 用于SPECIAL_CASES(在查找时不是O(1))。

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

【问题讨论】:

  • @quamrana:这篇文章不是关于“如何查找对象是否可散列”,我知道该怎么做,它是关于 dict/set 查找机制的怪癖
  • 听起来好像python看到:obj in dict_or_set,那么它首先尝试的是hash(obj)

标签: python hashtable lookup


【解决方案1】:

您无疑已经意识到,set 和 dicts 的内部工作原理非常相似。基本上这个概念是你有键 - 值对(或者只是带有集合的键),并且键永远不能改变(不可变)。如果一个对象是可变的,散列就会失去它作为底层数据唯一标识符的意义。如果您无法判断一个对象是否唯一,那么一组唯一键的含义就会失去其唯一性的关键属性。这就是为什么可变类型不允许在集合中和作为字典的键的原因。以您的示例:{} in {} # empty dict not in empty dict 我认为您有一点误解,因为dict.__contains__ 只检查字典的键,而不是值。由于您永远不能将 dict 作为键(因为它是可变的),因此这是无效的。

【讨论】:

  • 我知道你写的一切,key in dct 检查key 是否在dct 中,而不是在值中,我的问题是为什么它会引发TypeError,而不是@987654327 @(因为不可散列的对象不能在 dict 键/set 元素中)以及在一般情况下如何处理
  • @AzatIbrakov 鉴于这种逻辑,这应该有效吗? a = [1,2,3]; print(a['key'])。我在这里看到的问题是在静态类型语言中完全没有实际意义的问题,而在动态语言中则是常见问题。有些操作需要知道类型。我认为这是编程的正常部分,而不是语言错误。
  • @Aaron:没有实际意义。
  • @quamrana ty.. 还没有消化我早上的咖啡:P
  • __getitem____contains__ 是完全不同的方法,所以我认为您的示例在某种程度上并不相关
【解决方案2】:

我在 Python 错误跟踪器上找到了 this issue。长话短说:

如果

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

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

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

所以我想我会在可能发生这种情况的地方编写和使用适当的实用程序。


关于错误的根源:在CPython repodict___contains__ function(这是dict.__contains__方法实现)调用PyObject_Hash function(对应于hash function)->对于不可散列的对象(如{}我们的第一个案例)调用PyObject_HashNotImplemented function -> 生成此错误。

【讨论】:

    猜你喜欢
    • 2012-10-27
    • 1970-01-01
    • 2017-08-01
    • 2018-03-13
    • 2019-11-13
    • 1970-01-01
    • 2019-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多