Python对象哈希规则_set与dict说明【指导】

Python中可哈希对象需满足哈希值不变且等值对象哈希值相同;默认可哈希的有int、float、str、bytes、None、元素全可哈希的tuple、frozenset;list、dict、set因可变不可哈希;自定义类需同时正确定义__hash__和__eq__。

Python中,setdict 的底层依赖对象的哈希值(hash())来实现快速查找和去重。能否放入 set 或作为 dict 的键,取决于该对象是否“可哈希”——核心是:对象必须满足 哈希值在整个生命周期内不变,且 相等的对象必须有相同的哈希值(即遵守 __hash____eq__ 的一致性约定)。

哪些对象默认可哈希?

不可变内置类型通常可哈希:

  • intfloatstrbytesNone
  • 元组(tuple)——但仅当其所有元素都可哈希时才可哈希(例如 (1, "a", (2, 3)) ✅;(1, [2]) ❌)
  • frozenset(冻结集合)✅;普通 set ❌(因可变)

为什么 list、dict、set 不可哈希?

因为它们是可变容器:内容改变后,若哈希值不变,就会破坏哈希表结构(比如键查不到、重复插入);若强制改哈希值,又违反“哈希值不可变”原则。所以 Python 直接禁止它们实现 __hash__(返回 NotImplemented),调用 hash() 会报 TypeError

例如:

hash([1, 2])     # TypeError: unhashable type: 'list'
hash({'a': 1})  # TypeError: unhashable type: 'dict'
hash({1, 2})    # TypeError: unhashable type: 'set'

自定义类如何支持放入 set 或作 dict 键?

需同时定义 __hash____eq__,且保证逻辑一致:

  • __hash__ 应基于不可变属性计算(推荐用 hash((a, b, c)) 元组哈希)
  • __eq__ 必须用相同属性判断相等性
  • 若重写了 __eq__ 但没定义 __hash__,Python 会自动将 __hash__ 设为 None(即不可哈希)

示例:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
def __eq__(self, other):
    return isinstance(other, Point) and self.x == other.x and self.y == other.y

def __hash__(self):
    return hash((self.x, self.y))  # 基于不可变属性构造元组哈希

p1 = Point(1, 2) p2 = Point(1, 2) print(p1 == p2) # True print(hash(p1) == hash(p2)) # True s = {p1, p2} print(len(s)) # 1 —— 正确去重

常见陷阱与建议

  • 不要在 __hash__ 中使用可变属性(如列表、字典字段),否则哈希值可能随内容变化,导致 set/dict 行为异常
  • 若对象逻辑上应不可变,建议在 __init__ 后禁止修改关键属性(可用 __slots__ 或属性只读封装)
  • 调试时可用 hasattr(obj, '__hash__') and obj.__hash__ is not None 判断是否可哈希
  • 想临时用可变对象作键?可转成不可变表示,例如 dictfrozenset(items())listtuple()(确保元素可哈希)