Python可哈希与不可哈希对象原理:深入明确dict的键

[复制链接]
发表于 2026-4-8 09:32:50 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
# 🎯 Python可哈希与不可哈希对象原理:深入明确dict的键 在Python中,我们常常会遇到如许的错误:`TypeError: unhashable type: 'list'`。为什么会出现这个错误?什么是"可哈希"?为什么列表不能作为字典的键,而元组却可以?本文将深入探究Python中的哈希机制,帮你彻底明确这个概念。 ## 一、什么是哈希? 哈希(Hash)是将恣意长度的数据映射为固定长度值的算法。在Python中,`hash()`函数可以盘算对象的哈希值: ```python # 整数 print(hash(42)) # 42 # 字符串 print(hash("hello")) # 某个固定整数 # 元组 print(hash((1, 2, 3))) # 某个固定整数 # 列表 - 会报错! # print(hash([1, 2, 3])) # TypeError: unhashable type: 'list' ``` ## 二、可哈希对象的特性 一个对象要成为可哈希对象,必须满足以下条件: ### 1. 不可变性(Immutability) 可哈希对象必须是不可变的。由于哈希值是基于对象内容盘算的,如果对象内容改变了,哈希值也应该改变,这会导致严峻题目。 ```python # 字符串是不可变的,可哈希 s = "hello" print(hash(s)) # 正常 # 整数是不可变的,可哈希 n = 100 print(hash(n)) # 正常 # 列表是可变的,不可哈希 lst = [1, 2, 3] # print(hash(lst)) # TypeError! # 字典是可变的,不可哈希 d = {"a": 1} # print(hash(d)) # TypeError! ``` ### 2. 哈希值稳定性 对象的哈希值在其生命周期内必须保持稳定: ```python t = (1, 2, 3) h1 = hash(t) h2 = hash(t) print(h1 == h2) # True,元组的哈希值始终类似 ``` ## 三、为什么列表不能哈希? 想象如许一个场景: ```python # 假设列表可以哈希(现实上不可) my_dict = {} lst = [1, 2, 3] # 假设这行代码能实行 my_dict[lst] = "value" # 如今修改列表 lst.append(4) # 题目来了: # 1. 列表内容变了,哈希值应该变吗? # 2. 如果哈希值变了,字典怎么找到这个键? # 3. 如果哈希值稳定,dict[lst] 还能找到准确的值吗? ``` 这就是为什么可变对象不能作为字典键的缘故起因——会粉碎哈希表的完备性! ## 四、元组的特别情况 元组是不可变的,但元组中的元素必须是可哈希的吗? ```python # 元组自己不可变,且元素都可哈希 → 可哈希 t1 = (1, 2, 3) print(hash(t1)) # 正常 # 元组包罗列表 → 不可哈希! t2 = (1, 2, [3, 4]) # print(hash(t2)) # TypeError: unhashable type: 'list' # 元组包罗字典 → 不可哈希! t3 = (1, {"a": 2}) # print(hash(t3)) # TypeError: unhashable type: 'dict' ``` **结论**:只有当元组中的全部元素都是可哈希的,元组自己才是可哈希的。 ## 五、现实应用场景 ### 场景1:用元组作为字典键 ```python # 用坐标作为键 locations = { (0, 0): "原点", (1, 0): "东边", (0, 1): "北边", } print(locations[(0, 0)]) # 原点 ``` ### 场景2:聚集去重(须要可哈希对象) ```python # 可哈希对象可以放入聚集 unique_items = {1, "hello", (2, 3)} print(unique_items) # 不可哈希对象不能放入聚集 # invalid_set = {[1, 2], [3, 4]} # TypeError! # 须要将列表转换为元组 data = [[1, 2], [1, 2], [3, 4]] unique_data = set(tuple(item) for item in data) print(unique_data) # {(1, 2), (3, 4)} ``` ### 场景3:自界说类的哈希活动 ```python class Person: def __init__(self, name, age): self.name = name self.age = age def __hash__(self): # 基于不可变属性盘算哈希 return hash((self.name, self.age)) def __eq__(self, other): if isinstance(other, Person): return self.name == other.name and self.age == other.age return False def __repr__(self): return f"erson({self.name!r}, {self.age})" # 如今Person对象可以作为字典键 p1 = Person("Alice", 25) p2 = Person("Alice", 25) people = {p1: "Engineer"} print(people[p2]) # Engineer,p1和p2相称且哈希类似 ``` ## 六、判定对象是否可哈希 ```python def is_hashable(obj): try: hash(obj) return True except TypeError: return False # 测试 print(is_hashable("hello")) # True print(is_hashable(42)) # True print(is_hashable((1, 2))) # True print(is_hashable([1, 2])) # False print(is_hashable({"a": 1})) # False print(is_hashable((1, [2]))) # False,元组包罗列表 ``` ## 七、常见可哈希与不可哈希对象总结 | 可哈希对象 | 不可哈希对象 | |-----------|-------------| | int, float | list | | str | dict | | tuple(元素都可哈希) | set | | frozenset | 包罗可变对象的tuple | | bool | bytearray | | None | | ## 八、总结 明确可哈希与不可哈希对象,关键在于把握两点: 1. **不可变性**:可哈希对象必须是不可变的 2. **划一性**:对象的哈希值在其生命周期内保持稳定 把握这个概念后,你就能明确为什么某些对象不能作为字典键,以及怎样准确地筹划自己的类来支持哈希活动。 ## 参考资料 - [Python官方文档 - hash()](https://docs.python.org/3/library/functions.html#hash) - [Python官方文档 - __hash__](https://docs.python.org/3/reference/datamodel.html#object.__hash__) - 《Fluent Python》第3章:字典与聚集
免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表