关于Python字典的键的一些研究
- 前言
- 搬运
- 疑惑
- 研究
- 其他思考
前言
今天10月28日,RNG2:0二刷4AM战队,杀象杀象!!
起因是摸鱼的时候看了这篇文章,一些有趣且鲜为人知的 Python 特性,觉得很神奇就深入研究了下关于Python字典的 key
。
搬运
可能有人跟我一样懒得点链接,那我就搬过来好了。
class SomeClass(str):
pass
some_dict = {'s':42}
Output:
>>> type(list(some_dict.keys())[0])
str
>>> s = SomeClass('s')
>>> some_dict[s] = 40
>>> some_dict # 预期: 两个不同的键值对
{'s': 40}
>>> type(list(some_dict.keys())[0])
str
说明:
- 由于 SomeClass 会从 str 自动继承 __hash__ 方法, 所以 s 对象和 “s” 字符串的哈希值是相同的.
- 而 SomeClass(“s”) == “s” 为 True 是因为 SomeClass 也继承了 str 类 __eq__ 方法.
- 由于两者的哈希值相同且相等, 所以它们在字典中表示相同的键.
- 如果想要实现期望的功能, 我们可以重定义 SomeClass 的 __eq__ 方法.
class SomeClass(str):
def __eq__(self, other):
return (
type(self) is SomeClass
and type(other) is SomeClass
and super().__eq__(other)
)
# 当我们自定义 __eq__ 方法时, Python 不会再自动继承 __hash__ 方法
# 所以我们也需要定义它
__hash__ = str.__hash__
some_dict = {'s':42}
Output:
>>> s = SomeClass('s')
>>> some_dict[s] = 40
>>> some_dict
{'s': 40, 's': 42}
>>> keys = list(some_dict.keys())
>>> type(keys[0]), type(keys[1])
(__main__.SomeClass, str)
(完)
疑惑
众所周知,字典的键值必须是 hashable
的也就是可哈希的对象,那么如果想要我们自己定义的对象作字典的值,就必须继承或者自定义特殊方法 __hash__。
在上面的例子里面,SomeClass
继承于 str
,因此 SomeClass('s')
的哈希值肯定与 's'
的哈希值相等。
我以为这样就够了,以为键与键之间的比较通过比较哈希值是否相等就够了,结果上面说明里的 2/3
点还提到了值的比较,难道我一直以来的认知是错误的?于是我开始了搜索和实验。
研究
下面我自己声明了一个类:
class SomeClass():
def __init__(self, value):
self.value = value
def __repr__(self):
return repr(self.value)
def __hash__(self):
return hash(self.value)
开始了实验:
s = SomeClass('s')
some_dict = {'s':42}
some_dict[s] = 3
print(hash('s')==hash(s))
# True
print('s'==s)
# False
print(some_dict)
# {'s': 42, 's': 3}
果然,当只有哈希值相等而值不相等时,字典会认作两个键。
下面我再类里面添加了魔术方法 __eq__ :
def __eq__(self, other):
return self.value == other if type(self.value)==type(other) else False
然后这次的输出:
s = SomeClass('s')
some_dict = {'s':42}
some_dict[s] = 3
print(hash('s')==hash(s))
# True
print('s'==s)
# True
print(some_dict)
# {'s': 3}
果然,只有两者的哈希值相同且相等,它们在字典中才表示相同的键!
其他思考
- 为啥非要我在类里面补充 __eq__方法之后才会比较?
因为str
类的 __eq__ 方法里面只实现了相同类(也就是两个字符串)之间的比较,对于其他类的比较会返回NotImplemented
,于是会调用SomeClass
的 __eq__ 方法进行比较。