python / python/cpython

Exception notes discarded from TypeError within __hash__()

未關閉
#149,313 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

interpreter-core type-bug
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

Bug report

Bug description:

Context

I'm maintaining this library that requires users to construct deeply nested structures that are used as dict keys. To alleviate the annoyance of figuring out where a non-hashable value has been introduced into the key structure, I started using dedicated data structures with a __hash__(), which adds location information to the exception's notes:

from dataclasses import dataclass
from dataclasses import field

@dataclass
class KeyStruct:
    name: str
    first: object = field(repr=False)
    second: object = field(repr=False)

    def __hash__(self) -> int:
        try:
            h1 = hash(self.first)
        except TypeError as e:
            e.add_note(f"within {self}.first")
            raise

        try:
            h2 = hash(self.second)
        except TypeError as e:
            e.add_note(f"within {self}.second")
            raise

        return hash((self.name, h1, h2))

So when such a structure is used, information about the location of the offending value can be provided:

s = KeyStruct("foo", 1, KeyStruct("bar", 2, KeyStruct("spam", 3, [])))
s_dict = {s: "hello"}
Traceback (most recent call last):
  File "/Users/michi/Documents/Projects/Declarative_Python/declarative-python/src/hello.py", line 43, in <module>
    s_dict = {s: "hello"}
             ^^^^^^^^^^^^
  File "/Users/michi/Documents/Projects/Declarative_Python/declarative-python/src/hello.py", line 34, in __hash__
    h2 = hash(self.second)
  File "/Users/michi/Documents/Projects/Declarative_Python/declarative-python/src/hello.py", line 34, in __hash__
    h2 = hash(self.second)
  File "/Users/michi/Documents/Projects/Declarative_Python/declarative-python/src/hello.py", line 28, in __hash__
    h1 = hash(self.first)
TypeError: unhashable type: 'list'
within KeyStruct(name='spam').first
within KeyStruct(name='bar').second
within KeyStruct(name='foo').second

It is not very beautiful, but very helpful. :)

The Bug

The above output is from Python 3.13. The output changes with Python 3.14 (3.14.4 on macOS 14.8.5):

Traceback (most recent call last):
  File "/Users/michi/Documents/Projects/Declarative_Python/declarative-python/src/hello.py", line 43, in <module>
    s_dict = {s: "hello"}
             ^^^^^^^^^^^^
TypeError: cannot use 'KeyStruct' as a dict key (unhashable type: 'list')

I suspect that the source of this is the improved error messages introduced with https://github.com/python/cpython/issues/132825. If I interpret this code correctly, the dict and set methods that need to hash a key will catch and discard the raised TypeError and only re-use the exception message.

IMHO his is unfortunate and I consider it a regression.

I'm not sure what the best way to solve this would be:

  • As an experienced Python programmer, I would have expected the implementation of dict and set to attach the existing exception as __cause__ on the new exception. That would solve my use case.

  • An alternative would be to copy the notes from the old to the new exception. If you consider the notes a part of something I'll call the "extended exception message", the code would conceptually replace the exception, modifying, but retaining its "extended exception message" in full.

    But this sill has the drawback of discarding the original stack trace, which I also consider valuable in many cases.

  • Update the passage describing the improvement in What’s new in Python 3.14 to document the change in behavior and provide a workaround. E.g.:

    • Improved error message when trying to add an instance of an unhashable type to a dict or set. (Contributed by CF Bolz-Tereick and Victor Stinner in gh-132828.)

      (code block)

      In those instances, the original TypeError and its notes and stack trace are replaced. You can raise a subclass of TypeError from a __hash__() implementation to opt-out of this feature.

    AFAICT, the improvement is not documented elsewhere in Python's documentation.

CPython versions tested on:

3.14

Operating systems tested on:

macOS

Linked PRs
  • gh-149505

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

首先檢查報告中涉及的 dict 和 set 雜湊方法,以及 hash 進入點和相關的 PR gh-149505。比較 Python 3.13 和 3.14 的行為,包括例外說明和 traceback 處理。如果實作了針對該回歸問題的已接受解決方案,或在保留該行為時更新了 What's New,並補充了相關的回歸測試涵蓋範圍或文件,則視為完成。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
backend
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
停滯
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。