为啥字典键被转换为继承的类类型?
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】:虽然哈希值是相关的,但它不是主要因素。
在这里更重要的是平等。也就是说,对象可能具有相同的哈希值并且不相等,但相等的对象必须具有相同的哈希值(尽管这不是严格强制执行的)。否则在使用dict
和set
时会出现一些奇怪的错误。
由于您没有在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
并且都用作键。以上是关于为啥字典键被转换为继承的类类型?的主要内容,如果未能解决你的问题,请参考以下文章