Reuse stored hashes in dict.fromkeys() (#8503)

* Reuse stored hashes in dict.fromkeys()

Closes #8490.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make fromkeys_known_hashes a PyDict associated fn

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sanghun LeeandClaude Opus 5 authored and GitHub committed 2026-08-13 04:31:53 +09:00
1 parent 3a98ef746a
commit db5de5e5e2
3 files changed
+43 -5

No files matched your search

-1
View File
@@ -330,7 +330,6 @@ class TestJointOps:
name = repr(s).partition('(')[0] # strip class name
self.assertEqual(repr(s), '%s({%s(...)})' % (name, name))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_do_not_rehash_dict_keys(self):
n = 10
d = dict.fromkeys(map(HashCountingInt, range(n)))
+26 -4
View File
@@ -1,6 +1,6 @@
use super::{
IterStatus, PositionIterInternal, PyBaseExceptionRef, PyGenericAlias, PyMappingProxy, PySet,
PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set::PySetInner,
PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set, set::PySetInner,
};
use crate::common::lock::LazyLock;
use crate::object::{Traverse, TraverseFn};
@@ -9,7 +9,7 @@ use crate::{
TryFromObject, atomic_func,
builtins::{PyList, PyTuple, iter::builtins_iter, type_::PyAttributes},
class::{PyClassDef, PyClassImpl},
common::ascii,
common::{ascii, hash::PyHash},
dict_inner::{self, DictKey},
function::{ArgIterable, FuncArgs, KwArgs, OptionalArg, PyArithmeticValue, PyComparisonValue},
iter::PyExactSizeIterator,
@@ -354,6 +354,20 @@ impl PyDict {
) -> PyResult<Option<PyObjectRef>> {
self.entries.get(vm, key)
}
/// Keys of `obj` with their stored hashes, or `None` if it must be iterated
/// generically. Only exact dicts and sets qualify, as in CPython's
/// `_PyDict_FromKeys`: a subclass may override `__iter__`.
fn fromkeys_known_hashes(
obj: &PyObject,
vm: &VirtualMachine,
) -> Option<Vec<(PyObjectRef, PyHash)>> {
if let Some(dict) = obj.downcast_ref_if_exact::<Self>(vm) {
Some(dict.entries.keys_with_hashes())
} else {
set::exact_set_keys_with_hashes(obj, vm)
}
}
}
// Python dict methods:
@@ -384,8 +398,16 @@ impl PyDict {
let d = PyType::call(&class, ().into(), vm)?;
match d.downcast_exact::<Self>(vm) {
Ok(pydict) => {
for key in iterable.iter(vm)? {
pydict.__setitem__(key?, value.clone(), vm)?;
if let Some(keys) = Self::fromkeys_known_hashes(iterable.as_object(), vm) {
for (key, hash) in keys {
pydict
.entries
.insert_known_hash(vm, &*key, hash, value.clone())?;
}
} else {
for key in iterable.iter(vm)? {
pydict.__setitem__(key?, value.clone(), vm)?;
}
}
Ok(pydict.into_pyref().into())
}
+17
View File
@@ -605,6 +605,23 @@ fn extract_set(obj: &PyObject) -> Option<&PySetInner> {
})
}
/// Elements of `obj` with their stored hashes, or `None` unless `obj` is exactly
/// a `set` or `frozenset` — `PyAnySet_CheckExact`, where [`extract_set`] is the
/// subclass-inclusive `PyAnySet_Check`.
pub(super) fn exact_set_keys_with_hashes(
obj: &PyObject,
vm: &VirtualMachine,
) -> Option<Vec<(PyObjectRef, PyHash)>> {
let inner = obj
.downcast_ref_if_exact::<PySet>(vm)
.map(|set| &set.inner)
.or_else(|| {
obj.downcast_ref_if_exact::<PyFrozenSet>(vm)
.map(|frozen| &frozen.inner)
})?;
Some(inner.content.keys_with_hashes())
}
fn reduce_set(zelf: &PyObject, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
(
zelf.class().to_owned(),