Skip to content

set.discard() swallows TypeError raised by __eq__; set.remove() replaces it with KeyError #155102

Closed as not planned

Description

@BHUVANSH855

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

Activity

  1. picnixz commented on Aug 2, 2026

    @picnixz
    Member

    This is a GIGO case and this would break existing behavior (albeit wrong one). I do not think it is worth.

    Would the change be small though?

  2. BHUVANSH855 commented on Aug 2, 2026

    @BHUVANSH855
    ContributorAuthor

    The change looks limited to set_remove_impl() and set_discard_impl(). The shared set_discard_key() helper can stay unchanged since other callers don't clear/retry its error path.

  3. picnixz commented on Aug 2, 2026

    @picnixz
    Member

    What about regular objects that raise exceptions during __hash__ or in __eq__? how do they behave? why using a subclass of set is necessary? those questions should be answered first (and I expect you to do this kind of work yourself or ask your agent to do those kind of investigations first before posting an issue).

  4. BHUVANSH855 commented on Aug 2, 2026

    @BHUVANSH855
    ContributorAuthor

    I checked the other exception paths.

    Regular objects already propagate exceptions correctly:

    • hash exceptions propagate from both discard() and remove()
    • eq exceptions propagate from both discard() and remove()

    The inconsistent behavior only happens for a hashable set subclass key, because it enters the special unhashable-set fallback path in set_remove_impl() / set_discard_impl().

    After changing those paths to preserve the original error instead of clearing and retrying, both operations propagate the same TypeError as membership testing.

    So the change is limited to the special set/frozenset compatibility path.

  5. added a commit that references this issue on Aug 2, 2026
  6. ZeroIntensity commented on Aug 3, 2026

    @ZeroIntensity
    Member

    I don't know how to appropriately deal with these kinds of issues. Sure, we could probably get away with fixing this (there's always a risk of breakage -- is this really worth the risk?), but in practice, this affects precisely 0 people.

    By accepting it, we're setting a precedent. We are, in essence, saying that it's okay to use an LLM to scour CPython to find slightly questionable behavior that you can report and use to list on your CV. As a project, I don't think we should encourage that.

    If you're going to use an LLM, please use it for something useful. This is your 8th issue reporting a problem that has never occurred in practice. CPython has a limited number of reviewers; by sending us these, you're taking away valuable maintainer time from PRs that solve real-world issues, and it's not fair to those contributors or our users to continue entertaining this kind of behavior.

    Our AI policy specifically states that we may close issues that "are not useful or productive", and I think this falls under that category, so I'm going to reject this. For the future, I highly recommend taking a break from submitting issues. We have a backlog of about 7k open reports, many of which are affecting real Python users -- please look into those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions