Skip to content

set/frozenset re-hash elements when constructed from a set, frozenset, or dict #8489

Description

@fregataa

Summary

set/frozenset recompute __hash__ for every element when the source is a set, frozenset, or dict. CPython reuses the hash already stored in the source's entries (set_update_internal in Objects/setobject.c, branching on PyAnySet_Check / PyDict_CheckExact), so no extra __hash__ calls happen.

Scope

The set/frozenset target only. The dict target (dict.fromkeys) is #8490. test_do_not_rehash_dict_keys asserts on both directions, so its @unittest.expectedFailure # TODO: RUSTPYTHON marker can only be dropped once both are done.

Reproduction

class HashCountingInt(int):
    def __init__(self, *args):
        self.hash_count = 0
    def __hash__(self):
        self.hash_count += 1
        return int.__hash__(self)

d = dict.fromkeys(map(HashCountingInt, range(10)))
print(sum(e.hash_count for e in d))   # 10
frozenset(d)
print(sum(e.hash_count for e in d))   # CPython: 10, RustPython: 20

Lib/test/test_set.py:333 — TestJointOps.test_do_not_rehash_dict_keys, inherited by TestSet, TestSetSubclass, TestFrozenSet and TestFrozenSetSubclass (4 of the 10 expected failures in test_set.py).

Analysis

The hash is already stored — DictEntry keeps it next to the key (crates/vm/src/dict_inner.rs:227), and PySetInner::update_internal (crates/vm/src/builtins/set.rs:379) already special-cases AnySet and PyDict. But both branches drop it and go through self.add() → content.insert(), which re-hashes:

fn merge_set(&self, any_set: AnySet, vm: &VirtualMachine) -> PyResult<()> {
    for item in any_set.as_inner().elements() {
        self.add(item, vm)?;      // recomputes the hash
    }
    Ok(())
}

update_internal is also only reached from set.update() (:719). These paths never dispatch on the source type at all:

  • PySetInner::from_iter (:187), used by the set() / frozenset() constructors
  • union (:252), intersection (:261), difference (:272), symmetric_difference (:280)
  • intersection_update (:409), difference_update (:422), symmetric_difference_update (:436)

Activity

  1. added a commit that references this issue on Aug 11, 2026
    2924f86
  2. added a commit that references this issue on Aug 12, 2026
    7612e68
  3. added a commit that references this issue on Sep 16, 2026
    856c3da
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

    z-ca-2026Tag to track Contribution Academy 2026

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions