为啥字典键被转换为继承的类类型?

Posted

技术标签:

【中文标题】为啥字典键被转换为继承的类类型?【英文标题】:Why is the dictionary key being converted to an inherited class type?为什么字典键被转换为继承的类类型? 【发布时间】:2018-06-16 13:32:12 【问题描述】:

我的代码如下所示:

class SomeClass(str):
    pass

some_dict = 's':42

>>> type(some_dict.keys()[0])
str
>>> s = SomeClass('s')
>>> some_dict[s] = 40
>>> some_dict # expected: Two different keys-value pairs
's': 40
>>> type(some_dict.keys()[0])
str

为什么 Python 在更新字典 some_dict 时将对象 s 转换为字符串“s”?

【问题讨论】:

要问自己的问题是,“为什么这些实例的哈希值相同?” ... 因为s 是一个值为"s" 的字符串? @Satwik - 正是:-) 不过这是个好问题。 @Satwik “散列值”并非一无所知。它们等于对象的 __hash__ 方法返回的任何值。通过继承str 而不是实现__hash__ 本身,SomeClass 的行为与str 完全相同,因此hash('s')hash(SomeClass('s')) 返回完全相同的值。 class A(str): pass ; print(hash('s') == hash(A('s'))) ; True 【参考方案1】:

这是一个非常好的问题。首先,将(key, value) 对放入dict 时,它使用哈希函数获取key 的哈希值并检查该哈希码是否存在。如果存在,则dict 将对象与相同的哈希码进行比较。如果两个对象相等(__eq__(self, other) 返回True),那么它会更新值,这就是您的代码遇到这种行为的原因。

鉴于SomeClass 甚至没有被修改,所以's'SomeClass('s') 应该具有相同的哈希码并且's'.__eq__(SomeClass('s')) 将返回True。

【讨论】:

【参考方案2】:

虽然哈希值是相关的,但它不是主要因素。 在这里更重要的是平等。也就是说,对象可能具有相同的哈希值并且不相等,但相等的对象必须具有相同的哈希值(尽管这不是严格强制执行的)。否则在使用dictset 时会出现一些奇怪的错误。

由于您没有在SomeClass 上定义__eq__ 方法,因此您继承了str 上的方法。 Python 的内置函数允许子类化,所以__eq__ 返回 true,如果对象不是具有不同类型的话,否则它们是相等的。例如。 's' == SomeClass('s') 是真的。因此's'SomeClass('s') 等价于字典的键是正确的。

要获得您想要的行为,您必须重新定义__eq__ dunder 方法以考虑类型。然而,当你定义一个自定义的 equals 时,python 停止给你一个自动的 __hash__ dunder 方法,你也必须重新定义它。但在这种情况下,我们可以重用str.__hash__

class SomeClass(str):
    def __eq__(self, other):
        return (
            type(self) is SomeClass
            and type(other) is SomeClass
            and super().__eq__(other)
        )

    __hash__ = str.__hash__

d = 's': 1
d[SomeClass('s')] = 2

assert len(d) == 2
print(d)

打印:'s': 2, 's': 1

【讨论】:

谢谢你!非常大的 TIL,值得一提的是,如果您尝试打印 d['s'],您只会得到“第一个”结果(其中“第一个”取决于您的 python 版本:3.6 会给您第一个插入的值,而 3.5 和更低的第一个在内存中。最后一行还有一个错字print(d) 星期天的最佳答案 :) 和工作日的最佳答案。 如果确实有理由这样做,最好实际实现一个不同的 __hash__ 并避免与str.__hash__,特别是如果有多个类继承自 str 并且都用作键。

以上是关于为啥字典键被转换为继承的类类型?的主要内容,如果未能解决你的问题,请参考以下文章

当我将此数据帧转换为字典时,为啥索引设置不正确?

为啥 Python 中的字典和列表不继承 'len' 函数

Python中字典数据类型,字典.values()可以进行遍历,为啥不能通过角标进行获取元素?

为啥哈希中的这个字符串键被转换为符号?

将具有 nan 值的 str 类型字典转换为字典类型对象

如何将字节类型转换为字典?