CPython字典匹配短路内存指针 重载相等失效仍触发键覆盖

【python】自定义类型的对象,可以成为字典的键值么?这里有什么你从没想过的坑呢? — 码农高天

码农高天

CPython哈希查找机制在哈希值相同时会优先比对对象指针,只要内存地址一致便跳过__eq__直接判定命中,导致即使对象判等返回False仍会被字典视作同一键覆写。

AI-assisted summary of 【python】自定义类型的对象,可以成为字典的键值么?这里有什么你从没想过的坑呢? — original source. Check the original for full context.

Source published:

Key ideas

  • 自定义类默认直接继承 object 的基于内存指针(id)的哈希算法,因此属性相同的新对象作为字典键时不会互相覆盖,而是被作为两个独立键保存。 (0:00)
  • 在类中仅仅重写 __eq__ 方法会导致该类被 CPython 自动设置为不可哈希(__hash__ = None),再次作为字典键时会直接抛出 TypeError 异常。 (5:00)
  • CPython 字典在哈希匹配后具备指针级别的身份短路优化,若对象指针相同,底层不会调用 __eq__ 便直接判定为同一键,导致即便 __eq__ 恒返回 False 也会覆盖键值。 (10:00)

Detailed notes

## Background 该视频由“码农高天”于 2023 年 9 月 29 日发布在 YouTube 平台。视频围绕如何让 Python 自定义类型对象成为字典可用的键值展开,旨在揭示日常开发看似简单的语法背后鲜为人知的底层原理与技术陷阱。

## TL;DR 视频以二维坐标类作为案例,深入探讨了自定义对象充当字典键时的行为模式。作者首先展示了仅仅重写 `__eq__` 会导致 Python 自动屏蔽 `tp_hash` 并引发不可哈希异常,并通过 CPython 底层源码剖析了哈希表维护相等性不变性的规则。在给出同时实现属性元组哈希与相等性比较的标准解决方案后,视频进一步揭示了反直觉的极端场景:由于 CPython 字典内部存在指针级别的“身份短路”优化,即便对象的 `__eq__` 恒定返回 `False`,相同引用的对象在字典中依然会被判定为同一个键。

## Atmosphere & style 视频兼具硬核技术探究的深度与平易近人的幽默感。主讲人在自研工具意外翻车时淡定自嘲并保留未剪辑片段,展现了极具真实感的开发者日常;而随着从表层现象层层递进到 CPython 源码剖析,再到抛出颠覆直觉的极端测试案例,节奏环环相扣,为观众带来了抽丝剥茧般的智力解谜快感。

## Bulletin - [00:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=0s) 自定义类默认直接继承 object 的基于内存指针(id)的哈希算法,因此属性相同的新对象作为字典键时不会互相覆盖,而是被作为两个独立键保存。 - [05:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=300s) 在类中仅仅重写 __eq__ 方法会导致该类被 CPython 自动设置为不可哈希(__hash__ = None),再次作为字典键时会直接抛出 TypeError 异常。 - [10:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=600s) CPython 字典在哈希匹配后具备指针级别的身份短路优化,若对象指针相同,底层不会调用 __eq__ 便直接判定为同一键,导致即便 __eq__ 恒返回 False 也会覆盖键值。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=0s) 自定义类默认可以作为字典的键,因为其继承了 object 默认提供的哈希与比较行为。 - [00:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=0s) 默认情况下,相同属性的两个自定义类实例由于内存地址不同,在字典中会被视作完全不同的键。 - [02:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=120s) 仅定义 __eq__ 实现值的逻辑相等不仅无法合并字典键,反而会触发 unhashable type 错误使得对象彻底不可哈希。 - [05:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=300s) CPython 的基类 object 默认通过 _Py_HashPointer 计算哈希值,其核心基于对象的内存地址指针(即 id())。 - [06:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=360s) 为了维护“相等对象必须哈希相等”的哈希表不变性约定,CPython 在类型重载 rich comparison (__eq__) 且未定义 __hash__ 时,会自动屏蔽继承自父类的 tp_hash 槽。 - [07:30](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=450s) 即使手动定义 __hash__ 为 hash(id(self)),也无法解决等值键合并问题,因为不同实例计算出的哈希值依然互不相同。 - [08:30](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=510s) 正确将自定义类作为等值键的规范做法,是结合 __eq__ 逻辑,将参与判等的属性组合成元组后调用 hash((self.x, self.y))。 - [09:30](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=570s) 若仅定义 __hash__ 而未定义 __eq__,当哈希发生碰撞时,字典仍会退化为比对对象身份,无法合并逻辑相同的键。 - [10:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=600s) CPython 字典查找键时采用身份短路(Identity Short-circuit)优化:若哈希值相同且两者指针相同,将直接判定命中而跳过 __eq__ 调用。 - [11:30](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=690s) 当类的 __eq__ 恒返回 False 时,对同一对象的多个引用放入字典仍会触发键覆盖,呈现出 Python 代码判断 pos == pos2 为 False 但字典视为同一键的奇特行为。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=0s) 自定义对象作为字典键的初次尝试与 `__eq__` 引发的报错

在 Python 开发中,字典(`dict`)是非常核心且常用的数据结构,本质上保存的是键值对(key-value pair)。通常情况下,开发者习惯使用字符串、数字,甚至是只包含不可变(immutable)对象的元组(tuple)作为字典的键。然而,很多人都会面临一个疑问:我们自定义的数据结构到底能不能直接作为字典的键使用?

码农高天举了一个在二维网格游戏中极其常见的例子:定义一个表示二维坐标的自定义类 `Position`。通过定义这样的类型,开发者可以封装各种向量或坐标运算,简化代码逻辑。为了测试这个自定义类是否能当成字典的键,他编写了一个简单的测试场景:假设字典 `d` 用来记录每个坐标上存在的玩家数量,接着实例化了一个位于坐标 `(0, 1)` 的对象 `pos = Position(0, 1)`,并执行赋值操作 `d[pos] = 1`。代码顺利运行通过,没有任何报错,表面上看字典完美支持了自定义对象作为键。

为了更清晰地观察字典内部的结构,他原本打算使用自己开发的第三方库 `object_print` 打印对象详情。然而在尝试用新坐标 `pos2 = Position(0, 1)` 覆盖字典键(即执行 `d[pos2] = 2`)时,终端却意外触发了 `object_print` 的报错。面对这个突发的直播翻车现场,码农高天半开玩笑地自嘲道:“谢谢大家直接发现了一个 object_print 的 bug,我们不用 object_print 了,等我提个 issue 啊”,并为了保持视频的真实性决定保留这段未剪辑的内容。

```mermaid flowchart TD accTitle: 自定义类在字典中的键匹配演进 accDescr: 展示从默认行为、添加 eq、添加基础 hash 到正确哈希的设计变化流程

subgraph Step1["阶段一:默认继承自 object"] A1["pos = Position(0, 1)<br/>pos2 = Position(0, 1)"] --> B1["默认 hash(ID) 不同<br/>is/== 均为不同对象"] B1 --> C1["字典中存在 2 个不同的 key"] end

subgraph Step2["阶段二:仅重载 __eq__"] A2["仅定义 __eq__(self, other)"] --> B2["Python 自动将 __hash__ 设为 None"] B2 --> C2["抛出 TypeError: unhashable type"] end

subgraph Step3["阶段三:重载 __eq__ 与坐标 tuple 哈希"] A3["同时定义 __eq__ 与<br/>__hash__ = hash((self.x, self.y))"] --> B3["hash 相等且 __eq__ 返回 True"] B3 --> C3["成功覆盖,字典仅保留 1 个 key"] end ```

抛开工具的小插曲后,使用原生打印可以清楚看到字典中竟然同时存在两个键:`Position(0, 1)` 与 `Position(0, 1)`。尽管在人类的直观思维中这两个坐标的值完全一致,但在 Python 底层,`pos` 和 `pos2` 实际上是两个独立的 Python 对象,字典自身无法默认感知两者的逻辑等价性。有些开发者可能会认为只要重载 `__eq__` 方法比较 `self.x == other.x and self.y == other.y` 就能解决问题。然而当为 `Position` 类添加了 `__eq__` 方法后再次运行程序,结果反而变得更糟糕了——程序直接崩溃并抛出异常:“`TypeError: unhashable type: 'Position'`”。

- 默认情况下,没有任何定制的自定义类实例可以作为字典的键放入字典中,但相同的属性值不会被自动判定为同一个键。 - 仅仅为了实现“逻辑相等”而重定义 `__eq__` 函数,不仅不能解决多键覆盖问题,反而会触发 `unhashable type` 错误,导致对象彻底丧失充当字典键的资格。

---

[05:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=300s) CPython 源码剖析:`tp_hash` 的继承与覆盖机制

为了从根源上弄清楚为何加了 `__eq__` 就会导致对象变得不可哈希(unhashable),必须深入 CPython 的底层源码。在 CPython 中,字典设置键值对的核心实现位于 `PyDict_SetItem`。该函数在处理非 Unicode 键时,会调用 `PyObject_Hash` 去获取该对象的哈希值。

在 `PyObject_Hash` 内部,逻辑非常直截了当:检查对象所属类型的类型槽(type slot)`tp_hash`。如果存在,就调用它计算哈希;如果 `tp_hash` 为空或者未实现,系统就会抛出 `PyExc_TypeError`,其报错信息正是大家看到的 `unhashable type`。这引出了一个深层机制问题:为什么最开始没有定义任何哈希逻辑时程序能够正常计算哈希,而一旦定义了 `__eq__`,原本存在的哈希机制却消失了?

在 CPython 类型对象的继承逻辑(`inherit_slots`)中可以找到确凿的证据: 1. **基类默认哈希**:所有的 Python 自定义类都隐式继承自 `object`(即底层的 `PyBaseObject_Type`)。基类的 `tp_hash` 默认指向 `_Py_HashPointer`。该函数直接使用对象自身的内存地址指针(也就是 Python 层面的 `id()`)来进行哈希运算。正因为最初两个 `Position` 对象的内存地址不同,计算出的哈希值自然不同,所以在最开始的代码中字典会保存两个独立的键。 2. **`tp_hash` 的自动屏蔽**:在类型继承填充(`inherit_slots`)以及类构建逻辑中,Python 有一条专门规则:只有当该类型既没有重写比较方法(rich comparison,如 `__eq__`),也没有显式重载 `__hash__` 时,才会从父类继承 `tp_hash`。一旦开发者显式定义了 `__eq__`,Python 就会认为该类型的对象具有了基于值的相等性判定逻辑,因此不能再随意使用基于内存地址指针的默认哈希算法;为了防止打破“相等对象必须具有相同哈希值”的哈希表不变性约定,CPython 会主动将该类型的 `__hash__` 设为 `None`,并将其底层的 `tp_hash` 槽置空。

针对这个机制,码农高天用简单实验验证:如果手动添加一个 `__hash__` 方法并直接返回 `hash(id(self))`,报错确实消失了。但由于不同对象的 `id` 依旧不同,字典里仍然会有两个不同的键。因为哈希表查找元素的第一步是比较键的哈希值,如果哈希值不同,底层根本不会去调用 `__eq__` 判断相等性。“两个相等的对象,它们的哈希值必须相等”,这是哈希表能够正常工作的基石。

解决此问题的正确实践,是将影响对象逻辑相等的属性打包成一个不可变集合(例如 `(self.x, self.y)` 的 tuple),然后利用元组的内置哈希来实现自定义哈希逻辑:

```python def __hash__(self): return hash((self.x, self.y)) ```

当 `Position` 同时正确实现了匹配坐标值的 `__eq__` 和打包坐标的 `__hash__` 后,字典键的覆盖终于如期生效,字典最终只保留了一个键,其对应的值被成功更新为 2。相反,如果只保留 `__hash__` 而删掉 `__eq__`,字典在发生哈希碰撞时由于无法比对内容,默认退化为比对对象身份,依然会被当作两个键处理。

- CPython 的默认哈希算法由 `_Py_HashPointer` 实现,直接对对象的内存地址指针(即 `id()`)取哈希。 - 根据 Python 数据模型规定,一旦一个类重写了 `__eq__` 而没有定义 `__hash__`,该类将被自动视作不可哈希类型(unhashable)。 - 实现哈希表键的核心铁律:只要两个对象通过 `__eq__` 判定为相等,它们的 `__hash__()` 必须返回相同的整数。

---

[10:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=600s) 特殊情况探秘:同一对象判断与字典内部的身份短路

掌握了哈希与相等性的基本规则之后,码农高天抛出了一个极易让人产生认知偏差的极端案例。假设我们故意反常理而行之,让一个类的 `__eq__` 永远返回 `False`:

```python class Position: def __init__(self, x, y): self.x = x self.y = y

def __eq__(self, other): return False

def __hash__(self): return hash((self.x, self.y)) ```

在这个类中,任何实例之间使用 `==` 进行比较,结果都会是 `False`。接着,执行以下测试: 1. 实例化 `pos = Position(0, 1)`。 2. 令 `pos2 = pos`。由于 Python 中自定义对象赋值属于引用传递,`pos` 和 `pos2` 严格指向内存中的同一个对象(`pos is pos2` 为 `True`)。 3. 按照直觉测试 `pos == pos2`,由于 `__eq__` 永远返回 `False`,在 Python 解释器中运行 `pos == pos2` 得到的结果确实是 `False`。

但如果此时将它们作为字典的键进行连续操作: ```python d = {} d[pos] = 1 d[pos2] = 2 ```

此时控制台呈现了令人震惊的结果:字典不仅没有新增键,反而直接把 `d[pos]` 覆盖成了 2!整个字典中自始至终只有一个键。

码农高天指出了这一现象的奇妙与反直觉之处:“Python 认为 pos2 是不等于 pos 的,但是 Dictionary 认为,它们是同一个东西。”之所以发生这种现象,是因为 CPython 在实现字典和集合的哈希查找机制时加入了一层性能优化——身份短路(Identity Short-circuit)。当在哈希槽中比对一个新键和已有键时,底层的流程通常是: 1. 比对两个对象的 `hash` 值,如果不同直接跳过; 2. 如果 `hash` 相同,首先比较两者的内存指针(即 `self is other`); 3. **只有当指针不同时**,CPython 才会真正去调用 Python 层面的 `__eq__` 富比较函数。

因为 `pos` 与 `pos2` 指向的是同一块内存空间(同一个 C 结构体指针),底层在指针判等阶段就直接认定了两者是同一个键,压根不会去调用那个被故意改写成返回 `False` 的 `__eq__` 方法。

自定义类能否成为字典的键,以及在字典中展现出何种匹配行为,远比表面上的语法糖复杂得多。它不仅关乎 `__hash__` 和 `__eq__` 的常规设计模式,更涉及 CPython 为了兼顾安全与查找性能而在底层设定的机制规则。

- 在 Python 层显式比对 `pos == pos2` 时,会严格走方法分发逻辑调用 `__eq__` 并返回 `False`。 - 在 CPython 字典内部查找机制中,若两个键对象的哈希值相同且指针相同(`key1 == key2` 指针级别),字典会直接判定命中,从而跳过 `__eq__` 的调用。 - 设计可哈希自定义类时,必须保持哈希不可变性与相等逻辑的高度一致,否则会由于底层身份比对与哈希逻辑的交叉作用,引发极难排查的逻辑诡异行为。

## Notable quotes > [05:00](https://www.youtube.com/watch?v=d_ZxzjpK_LQ&t=300s) 两个相等的对象,它们的哈希值必须相等 — 码农高天

## Mentioned Python, CPython, 码农高天, object_print

## Video description 这期视频围绕着如何将自定义类型变成字典可用的键值展开,可能你知道这个问题的答案,但是你绝对想象不到这背后有多少内容你从没想过。我可以保证,看完这期视频,你一定会学到一些你不知道的知识!

[Source](https://www.youtube.com/watch?v=d_ZxzjpK_LQ)

中文简报

CPython字典匹配内存指针短路:重载相等失效仍触发键覆盖 CPython哈希查找机制在哈希值相同时会优先比对对象指针,只要内存地址一致便跳过__eq__直接判定命中,导致即使对象判等返回False仍会被字典视作同一键覆写。 视频以二维坐标类作为案例,深入探讨了自定义对象充当字典键时的行为模式。作者首先展示了仅重写 `__eq__` 会导致 Python 自动屏蔽 `tp_hash` 并引发不可哈希异常,并通过 CPython 底层源码剖析了哈希表维护相等性不变性的规则。在给出同时实现属性元组哈希与相等性比较的标准解决方案后,视频进一步揭示了反直觉的极端场景:由于 CPython 字典内部存在指针级别的“身份短路”优化,即便对象的 `__eq__` 恒定返回 `False`,相同引用的对象在字典中依然会被判定为同一个键。 自定义类默认直接继承 object 基于内存指针(id)的哈希算法,因此属性相同的新对象作为字典键时不会互相覆盖,而是被作为两个独立键保存。 (0:00) 在类中仅重写 __eq__ 方法会导致该类被 CPython 自动设置为不可哈希(__hash__ = None),再次作为字典键时会直接抛出 TypeError 异常。 (5:00) CPython 字典在哈希匹配后具备指针级别的身份短路优化,若对象指针相同,底层不会调用 __eq__ 便直接判定为同一键,导致即便 __eq__ 恒返回 False 也会覆盖键值。 (10:00)

Reading summary

码农高天指出,Python 自定义对象能否作为字典键以及如何判定键相等,取决于数据模型对 `__eq__` 与 `__hash__` 的联动约束,以及 CPython 底层的身份短路优化机制。 重载 __eq__ 导致不可哈希的机制 (5:00) 在默认情况下,继承自 `object` 的自定义类可以直接作为字典键,但由于底层继承的 `tp_hash`(由 `_Py_HashPointer` 实现)直接基于对象内存地址指针 `id()` 计算,属性值相同的两个不同实例会被判定为独立的键。若开发者仅为该类重写 `__eq__` 判定属性相等,CPython 在类型构建与槽继承时会自动将该类的 `__hash__` 设为 `None`,并清空底层 `tp_hash` 槽,再次作为字典键使用时便会抛出 `TypeError: unhashable type`。 这一设计的核心目的是维护哈希表“两对象若相等则哈希值必须相等”的不变性。若允许基于值的 `__eq__` 继续沿用基于内存地址的默认哈希,将破坏哈希表的查找一致性。要让相同属性的实例正确覆盖字典键,必须同时实现 `__eq__`,并将相关属性打包(例如元组)后显式实现 `__hash__` 返回一致的整数。 字典查找中的身份短路优化 (10:00) 在 CPython 字典内部查找机制中,判断键是否命中并非仅依赖 `__eq__`。高天通过一个极端测试说明该逻辑:若将类的 `__eq__` 硬编码为恒返回 `False`,在 Python 层执行 `pos == pos2`(指向同一对象的两个变量)确实返回 `False`;但若将 `pos2` 写入已被 `pos` 占用的字典键时,字典却会直接覆盖原值,维持单键状态。 这是因为 CPython 在哈希槽比对中加入了身份短路优化:比对两个键时,首先校验哈希值,哈希相同时先进行底层 C 指针比较(即 `is` 对象身份)。当两个键指向同一内存地址时,底层会直接判定命中,跳过 Python 层面的 `__eq__` 调用,从而出现“语言层面不相等、字典判定为同一键”的反直觉现象。 设计用于字典键的自定义类时,必须严格保证 `__eq__` 与 `__hash__` 的逻辑一致性并绑定不可变属性;忽略底层基于指针的身份短路与哈希屏蔽规则,容易导致隐蔽的覆盖异常或类型错误。

Source quotes

[5:00] 码农高天: 两个相等的对象,它们的哈希值必须相等

Original transcript

[0:00] 我们知道Dictionary就是字典是python中非常常用的一种数据结构那当我们在使用字典的时候呢我们本质上是使用了一个key value pair 对不对那么key也就是我们说的键值大部分时候在python里面我们使用的是string是字符串我们知道其实也可以使用比如说数啊甚至是由这个Immutable组成的tuple啊都是可以的好那现在问题来了到底什么东西可以成为字典的key也就是见值呢我能不能让我自定义的数据结构被当成key来使用呢今天我们就来把这件事讲明白好我们看左边我定义了一个自定义的类型啊叫做position那么这个呢是我在打一些二维的网格的游戏的时候非常非常常定义的一个类型有了这个类型的话你就可以在这个类型上定义很多的运算然后让很多事情变得相对比较简单那我们看假设现在我们有一个dictionaryd对吧这个d比如说我代表的是呃每一个位置上面有多少个玩家好了我们随便给他定一个事儿然后pods就是一个position是01这个位置我们很直观的一个想法就是d part等于1对吧Position01这个位置有一个玩家但是如果我们这么写的话程序会出问题吗我们运行一下一切正常我们成功了对吧这期视频根本就没有必要做嘛事实是这样的吗我们首先把这个 dictionary打印出来可以看到输出的部分和我们的期待是一样的 这个dictory里面key是一个object 然后value是1那这个object看起来不是那么直观啊我们用一下object rateobject print显示确实是我们这个position对吧 x0 y1 然后它的value是1那我们知道dictionary在对相同的key做操作的时候呢是可以覆盖这个key的内容的换言之如果我们继续对这个position01做操作的话我们的期待是可以改变这个value那我们写这么一个代码大家看一下我又重新建立了一个position 叫position2它的位置跟position是一样的 这两个object应该是一样的然后我让 D position 2 等于 2我们看一下什么结果谢谢大家谢谢大家直接发现了一个object print的bug我们不用object print的了等我提个issue啊我们先不用object print了呃好不好意思啊为了保持这个项目的真实性啊刚才那一段我就不剪掉了啊大家可以看看object print上现在会有一个新的issue啊就是刚才那个bug我们回归正题好 我们现在可以看到这个dictionary现在有两个key了这件事情也没有那么难理解就是position 1跟position 2 它们是两个object 对不对虽然我们的脑海中觉得他们俩是一样的但是确确实实是两个Python object那Dictionary本身是没有能力判断这两个东西是不是同一个object的所以就会出现这样一个我们不是很想要的结果明明两个我们认为是同一个object的东西它却成为了一个Dictionary里面的两个建制有的小伙伴说了这题我会对吧python是可以知道这两object是不是相等的我们只要overload它的eq函数就行了我们试一下好 我们写了一个一个函数 函数很简单 如果我的x跟他一样 y跟他也一样 我觉得我们俩是相等的那加了eco函数之后解决这个问题了吗我们再运行一下还不如刚才呢刚才好歹把这两个东西都放进Dictionary了现在直接给我报错了 告诉我Position上按HashableType但是刚才为什么没有报错为什么我加了一个equal函数之后它反而报错了呢想要真正的理解这个问题我们还是要回到Cpython的圆满好我们看这块是Pydict setItem啊这块呢就是我们每次用比如说Dictionary方括号一个什么东西的时候我们把一个key设置成一个value的时候就会调用这个函数那么这个函数呢核心我们就看这个部分啊如果他不是这个key不是一个unicode的话就会进到这个部分来然后要取一个py object cache我们近拍object cache看一下那拍object hash其实也不复杂就是如果你有hash函数的话我就求一个hash对吧 如果没有的话就会报一个错 是叫py object hnot implemented那如果大家不知道hash是什么东西的话回去补一下数据结构基础啊我们这里就不细讲了我们简单看一下hash not implemented哎这个部分的报错内容unhashable type什么东西就是我们刚刚看到的那个报错也就是说当一个object他没有hash function的时候我们如果要尝试把它作为dictionary的key就会报这个unhashable type的错问题来了当我们没有设置这个EQ函数的时候 [5:00] dashboard We try to make it as a key, it's work That in this time, there was a special thing that was supposed to be equal to break it? We put the target to the tp hash code If you saw a few times C Python, you will know what the Tp is called the object. some of the two-dimensional objects Tp hash is when it's time to use hash number of times, it's time to use the number of times. The first question is: I have no idea of the object to any of the hash-signers. Why is it first? Well, the answer is that we have every Python class in Python, and we have all the base objects. So BaseObject is a sign of hash hash just the pi hash pointer We can see it's called a pi hash pointer raw, right? It's using this object's arrow or the name of the name or if you're using Python, it's its ID to do hash That we know that the object's name is different because it's two objects, right? So the hash is different So at the beginning of the process, the dictionary has two different positions we think are the key How? I'll be here. There is a hash function. Why did we have a equal function? But I just got this. This is the Inherit Slot's number. It's when I'm going to be It will be used to be a lot of g-lay of the number We can see that only rich compares hash is now, at the same time, no overwrite hash I will be able to build base with hash and hash That you said I don't have overwrite hash, right? Let's see what overwrite hash is. This is very clear, right? If you have defined equal numbers or is defined by hash-sand-sus all will be thought that your hash function is overriding It's because we have defined the equal method It's not a hash function. It will cause it hash function to be able to become dictionary key That we can see that the C is using ID to do hash. That we can also use ID to do hash. Well, we have a very simple hash method. Hash method is hash We just use the ID to do hash This is not the same behavior as the one Let's try it We will find that even if we have defined equal values We also have a hash code But dictionary still has two key But the same thing is that because we have defined high-channel, at least it will not be done. What is the reason? Because HashMap is a good idea It must be a certain number of two equal points. The hash is the same. He will first go to the hash If hash is not the same, it doesn't think that these two fields are probably equal to That is why it's a good thing for the hash function to be good. We will always use the system to give hash function to hash function to hash function. As a hash function. This is a bit boring, let's give a example. I will see my x and y to the tuple and then do it again hash 把这个hash值作为我这个自定义object hash So I can ensure that when the position is equal to two positions, they will be the same We'll go to this one We finally got our results right? It's just one key and the value is 2 This is correct, and we want to do it There are some people will be curious, right? If I already have hash, I don't have equal to this. Let's try to do this. We'll just take a break. And then, the code is done We will find it becomes 2K, right? If you understand hash table's data from the fact that this is a very easy thing to understand because the two different types of data can be used to have the same hash This is hash collision, right? So in the last half, you must be able to compare the two data as well as the same That's not a particular case of equal. The two data are the same. It's not a particular case. [10:00] E aí Paws com Paws 2 não é o objeto 当然就不一样 那么Dictionary就认为它们两个不是一个东西 自然就会认为他们是两个key Então você pode ver que já esteja como se tornou a melhorar Quando você vai usar o código de uma linha de um código, você vai ser um dos dois de um código de um código E aí, e aí, e aí, e aí 如果hash一样同时也equal 就是一个键 É assim? Vamos ver um exemplo Esse exemplo você pode ver, você pode ver que esse tipo de pessoas tem um problema, não é? Porque ele é igual, ele retornou um falso Eu sou eu com quem não se torna, eu sou um dos. O primeiro, eu primeiro estabilize o o o o o o E eu vou deixar o parque 2 para parque 2 Nós sabemos que Python nos dizemos de objeto mutable 所以Path2跟Path根本指向的就是同一个东西 Então, vamos desenhar o pos2 e pos2 não é igualado E vamos fazer o díctio de forma de forma 神奇的事情发生了 Python认为 pos2是不等于 pos的 mas Dictionary认为, eles são os dois dois. Sim, Python é tão tão神奇 Como解释 esse problema? Eu vou fazer o cara de Nair, não é? E é em diccionário para o seu trabalho e depois ele vai começar a ser mais rápido e depois ele vai começar a ser mais rápido Isso é a gente já explicou a mais estranho de class definição não é tão tão simples Se você tem alguma coisa que você tem que falar de conteúdo, pode ir no botão ou no vídeo da descrição, eu vou ver se você. Bom, esse vídeo é aqui, se você gostou de gostar, se inscreva no canal e se inscreva no canal. E aí