【问题标题】:unexpected behaviour of dictionary membership check字典成员检查的意外行为
【发布时间】:2018-02-06 17:53:35
【问题描述】:

无法将不可散列的对象插入到字典中。它已记录在案,这是有充分理由的。

但是,我不明白为什么它会影响会员资格测试:

if value not in somedict:
    print("not present")

我假设成员资格测试只能返回 TrueFalse。但是当value 不可散列时,它会以TypeError: unhashable type 失败。我想说True 应该是这个not in 测试的正确答案,因为value 显然不包含在somedict 中,并且它不能插入是完全不相关的。

另一个例子:

try:
    result = somedict[value]
except KeyError:
    # handle the missing key

当该值不可散列时,它会以TypeError: unhashable type 失败,我希望改为KeyError

还有:

somedict.get(value, default)

不返回默认值,而是抛出TypeError


那么为什么unhashable in somedict 不评估为False 以及只返回True 或False 的正确测试是什么?


更新:

object.__contains__(self, item)

调用以实现成员资格测试运算符。如果 item 在 self 中,则返回 true,否则返回 false。

(来自“数据模型 - Python 文档”)


附录:

这是用户界面程序的简化部分,当其中一个参数是 dict 时失败。

# args = list of function arguments created from user's input
# where "V1" stands for value1 and "V2" stands for value2
XLAT = {'V1': value1, 'V2': value2} 
args = [XLAT.get(a, a) for a in args]
function(*args)

【问题讨论】:

标签: python dictionary


【解决方案1】:

原因是作为字典一部分的潜在键的测试是通过生成潜在键的哈希值来完成的。如果潜在键不能提供哈希值(如果对象不可哈希),则无法进行测试。

在某种程度上,你是对的,在这种情况下,存在测试只能说“不,不存在”(“因为它无论如何都不能插入”)。

这不是这样做的,因为它可能掩盖很多编程错误。

如果你干净地编程,你很可能永远不会检查一个不可散列的对象,无论它是否在字典中。 IE。它需要相当多的幻想才能想出一个你实际上会想的案例。 (不过,我不会说这完全不可能。)由于编程错误导致您不小心做某事的情况而发生这种检查的情况要大得多。所以异常表明你应该看代码中的那个点。

如果您知道自己在做什么(可能是您的情况),您应该简单地捕获该错误:

try:
    if strange_maybe_unhashable_value in my_dict:
        print("Yes, it's in!")
    else:
        print("No, it's not in!")
except TypeError:
    print("No, it's not even hashable!")

如果您想将其与您的 KeyError 处理相结合:

try:
    result = somedict[value]
except (KeyError, TypeError):
    # handle the missing key

try:
    result = somedict[value]
except KeyError:
    # handle the missing key
except TypeError:
    # react on the thing being unhashable

提供另一个相当深奥的方面:

一个对象可能在某个时间是可散列的,而在另一个时候是不可散列的(可能稍后)。这当然不应该是这种情况,但可能会发生,例如。 G。如果哈希值依赖于外部的东西。尽管有共同的假设,但可散列独立于不可变(尽管一个经常依赖于另一个)。所以一个对象可以在作为字典的一部分时改变,而这个可以改变它的哈希值。虽然这本身就是一个错误,并且会导致字典无法正常工作,但这也可能是一个设计原因,不能简单地说“不存在”或提出KeyError,而是提出TypeError

【讨论】:

  • 很好的解释,但预先明确检查对象是否可散列并采取相应措施会更清晰、更易读。异常应该针对异常,而不是针对预期的控制流。使用KeyError 而不是检查是否存在同样的问题。
  • “预期流量控制”和“异常”之间的界限在哪里?至少在具有廉价例外的 Python 中,将它们用于您真正期望的东西是完全正常的。在其他语言(例如 Java)中,情况有所不同,因为异常的代价非常高。
  • 通常这条线在典型用例与错误用例之间的某处。在这种情况下,不可散列的对象显然不是错误(不是实现错误,也不是业务错误),那么为什么要让它成为异常呢?它是否便宜并不重要——我只是指可读性。
  • @BartoszKP 请求宽恕是 Python 中一个非常典型的习语而不是 LBYL...
  • @Alfe 这个解释听起来很合理。您能否提供此设计决策的链接或其他来源?
【解决方案2】:

也许你可以检查它是否是可散列的,如果是:试试你的代码,如果不是:返回 False。这不是您“为什么”问题的答案,但至少我认为它会起作用。

【讨论】:

    【解决方案3】:

    如果您尝试测试一个不可能存在于 dict 中的键,则表明您尝试针对错误的变量进行测试时出现逻辑错误或拼写错误。如果它永远不可能是真的,你为什么还要尝试测试?您可能想在代码中解决该问题。

    对我来说,将这两种不同的情况视为真正有助于调试的情况是有意义的。如果您确实有混合可散列和不可散列类型的情况,并且这显然没有错误,您应该明确说明:

    try:
        if value not in somedict:
            ...
    except TypeError:
        ...
    

    【讨论】:

      【解决方案4】:

      我认为这更像是一个实施决策。这是有道理的:不可散列的类型应该返回特定类型的错误。

      假设您实现了一个Counter 类,其中包含一个仅计算对象的Dictionary

      cnt.Add(x) # adds one to the counter
      cnt.Count(x) # returns the number of occurences of x as seen by cnt, 0 if x has never been seen beofreL.
      

      您在一组对象上运行它,其中一个恰好是不可散列的,但您不知道。那么当你想查看这个对象的出现次数时,你的程序会返回 0,这是不正确的。

      另一方面,当类型不可散列时存在特定异常这一事实让开发人员选择了要实现的行为。如果这两种情况只有一个例外,你就没有这个选择。

      【讨论】:

        【解决方案5】:

        将其用作:

        if value not in somedict.values():
            print("not present")
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-05-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多