Bug report
Bug description:
Bug description
When a hashable subclass of set is used as the lookup key, set.discard() silently swallows a TypeError raised from __eq__, while set.remove() replaces the original exception with KeyError.
The issue can be reproduced on current main.
Minimal reproducer
class Bad:
def __hash__(self):
return 1
def __eq__(self, other):
raise TypeError("boom from __eq__")
class HashableSet(set):
def __hash__(self):
return 1
s = {Bad()}
probe = HashableSet()
print("membership:")
try:
probe in s
except Exception as exc:
print(type(exc).__name__, exc)
print("\ndiscard:")
try:
s.discard(probe)
except Exception as exc:
print(type(exc).__name__, exc)
else:
print("returned normally")
print("\nremove:")
try:
s.remove(probe)
except Exception as exc:
print(type(exc).__name__, exc)
Actual behavior
membership:
TypeError boom from __eq__
discard:
returned normally
remove:
KeyError HashableSet()
Expected behavior
The original exception raised from __eq__ should propagate consistently.
TypeError: boom from __eq__
should be raised by both set.discard() and set.remove(), just as it is during the corresponding membership test.
Additional investigation
I reproduced this on current main.
For debugging, I temporarily instrumented set_remove_impl() and set_discard_impl() immediately before their PyErr_Clear() calls. At that point, the active exception is still:
TypeError('boom from __eq__')
indicating that the original exception raised by __eq__ reaches those sites before being cleared.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Bug description:
Bug description
When a hashable subclass of
setis used as the lookup key,set.discard()silently swallows aTypeErrorraised from__eq__, whileset.remove()replaces the original exception withKeyError.The issue can be reproduced on current
main.Minimal reproducer
Actual behavior
Expected behavior
The original exception raised from
__eq__should propagate consistently.should be raised by both
set.discard()andset.remove(), just as it is during the corresponding membership test.Additional investigation
I reproduced this on current
main.For debugging, I temporarily instrumented
set_remove_impl()andset_discard_impl()immediately before theirPyErr_Clear()calls. At that point, the active exception is still:indicating that the original exception raised by
__eq__reaches those sites before being cleared.CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs