Skip to content

gh-141510, PEP 814: Add built-in frozendict type#144757

Open
vstinner wants to merge 10 commits intopython:mainfrom
vstinner:frozendict_base
Open

gh-141510, PEP 814: Add built-in frozendict type#144757
vstinner wants to merge 10 commits intopython:mainfrom
vstinner:frozendict_base

Conversation

@vstinner
Copy link
Member

@vstinner vstinner commented Feb 12, 2026

Add TYPE_FROZENDICT to the marshal module.

Add C API functions:

  • PyAnyDict_Check()
  • PyAnyDict_CheckExact()
  • PyFrozenDict_Check()
  • PyFrozenDict_CheckExact()
  • PyFrozenDict_New()

Add PyFrozenDict_Type C type.


📚 Documentation preview 📚: https://cpython-previews--144757.org.readthedocs.build/

Add TYPE_FROZENDICT to the marshal module.

Add C API functions:

* PyAnyDict_Check()
* PyAnyDict_CheckExact()
* PyFrozenDict_Check()
* PyFrozenDict_CheckExact()
* PyFrozenDict_New()

Add PyFrozenDict_Type C type.
@vstinner
Copy link
Member Author

cc @corona10

Fix also indentation.
@corona10 corona10 self-requested a review February 13, 2026 10:07
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
}

if (PyDict_CheckExact(d)) {
if (PyAnyDict_CheckExact(d)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe same opinion with @kumaraditya303 we can avoid a lot of critical section if dict is frozen dict.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I would prefer to make such "optimization" change in a separated PR.

dict_length(PyObject *self)
{
return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)self)->ma_used);
return GET_USED(_PyAnyDict_CAST(self));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to use atomic operation for frozendict?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we can skip the atomic operation for frozendict.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It uses relaxed atomic so it compiles to simple loads so avoiding it has no real benefit.

};

static PyMappingMethods frozendict_as_mapping = {
.mp_length = dict_length,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we can make frozendict_lenth

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to make such optimization in a separated PR.


static PyMappingMethods frozendict_as_mapping = {
.mp_length = dict_length,
.mp_subscript = dict_subscript,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still perfer to make frozendict_subscript, but let's do it at the separate PR.

Copy link
Member

@corona10 corona10 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should notice that frozendict is not subclass of dict to follow SC feedback

vstinner and others added 3 commits February 13, 2026 15:46
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Document frozendict after lazy import.
mp->ma_values == NULL &&
(mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3))
(mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3) &&
!frozendict)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might take this fast-path for frozendict as well. It's just a missed optimization opportunity.

@corona10: Add it to the optimization TODO list :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I will take a look at it

if (items == NULL) {
return -1;
}
PyObject *frozenset = PyFrozenSet_New(items);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation creates an actual frozenset object which emits surprising error messages:

>>> hash(frozendict(x=[]))
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    hash(frozendict(x=[]))
    ~~~~^^^^^^^^^^^^^^^^^^
TypeError: cannot use 'tuple' as a set element (unhashable type: 'list')

We may change the implementation later to not create an actual frozenset, but reuse frozenset hash code instead, to avoid the surprising errors.

@vstinner
Copy link
Member Author

We should notice that frozendict is not subclass of dict to follow SC feedback

Ah right, done.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants