You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently when allocating a plain Python object, we allocate the object, and its values array.
This has a few downsides:
We perform two allocations instead of one.
An extra indirection is needed when accessing an attribute on the object
If the __dict__ is materialized we can no longer use specialized lookup.
We could fix three with an extra pointer in the object header, but that would waste more space.
We should append the values array directly after the object header, which fixes the above issues.
It adds some complexity, as we need to track ownership of the values so that they are freed exactly once, but may enable some simplifications as well. Overall, it would seem to make little difference to complexity.
Posting it here in case it was missed. #123192 caused mypyc to break. We haven't been able to narrow it down to a clean reproducer just yet unfortunately. For more details see python/mypy#17973
This breaks pybind11, as well (assuming mypyc is still broken since that's still open). We used to be able to specify a class accepted dynamic attributes, something like this:
Interestingly it's also a pickle test that's failing.
A workaround for mypyc would be to remove the Py_TPFLAGS_INLINE_VALUES, I just haven't looked into it yet if that's the right call. We remove other flags as well already.
#if PY_MINOR_VERSION == 11
// This is a hack. Python 3.11 doesn't include good public APIs to work with managed
// dicts, which are the default for heap types. So we try to opt-out until Python 3.12.
t->ht_type.tp_flags &= ~Py_TPFLAGS_MANAGED_DICT;
+#elif PY_MINOR_VERSION == 14+ t->ht_type.tp_flags &= ~Py_TPFLAGS_INLINE_VALUES;
The problem occurs when you make the class, then try to assign a new dictionary to __dict__ (which is what pickle does).
I tried removing the Py_TPFLAGS_INLINE_VALUES, but it didn't do anything, and I'm not sure it should. The code here doesn't check to see if it's set, it only checks the size and then then forces it to be inline, setting it if it's not already set. Maybe it should check for Py_TPFLAGS_MANAGED_DICT besides just checking the size? This patch fixes it for me:
Edit: Fixed, I forgot PyObject_VAR_HEAD. This reproduces the bug: with Python 3.13, it works, 3.14b1, it fails.
I did test that if we change the pickle implementation to do __dict__.update instead, it works. So it's just __dict__ = {...} that's broken; __dict__ updates, but the attributes don't update.
There is custom code in PyObject_GenericSetDict that is supposed to be handling the inline case, but maybe it wasn't hit before and is faulty?
Activity
GH-115776: Embed the values array into the object, for "normal" Pytho…
encukou commentedon Apr 11, 2024
This change introduced a regression, see #117750
GH-115776: Static object are immortal, so mark them as such. (GH-117673)
pythonGH-115776: Embed the values array into the object, for "normal"…
pythonGH-115776: Static object are immortal, so mark them as such. (p…
GH-115776: Allow any fixed sized object to have inline values (GH-123192
pythonGH-115776: Allow any fixed sized object to have inline values (p…
encukou commentedon Aug 29, 2024
The new public API,
Py_TPFLAGS_INLINE_VALUES, should be documented.cdce8p commentedon Jan 16, 2025
Posting it here in case it was missed. #123192 caused mypyc to break. We haven't been able to narrow it down to a clean reproducer just yet unfortunately. For more details see python/mypy#17973
henryiii commentedon May 9, 2025
This breaks pybind11, as well (assuming mypyc is still broken since that's still open). We used to be able to specify a class accepted dynamic attributes, something like this:
But now those attributes no longer are present. Is there something we should be doing instead?
This shows up in two tests. One is a pickling test; the dynamic attributes don't come back when the object goes through pickling.
The second test indicates you can no longer replace the dict.
cdce8p commentedon May 9, 2025
Yes, it still is. Tracked in
Interestingly it's also a pickle test that's failing.
A workaround for mypyc would be to remove the
Py_TPFLAGS_INLINE_VALUES, I just haven't looked into it yet if that's the right call. We remove other flags as well already.#if PY_MINOR_VERSION == 11 // This is a hack. Python 3.11 doesn't include good public APIs to work with managed // dicts, which are the default for heap types. So we try to opt-out until Python 3.12. t->ht_type.tp_flags &= ~Py_TPFLAGS_MANAGED_DICT; +#elif PY_MINOR_VERSION == 14 + t->ht_type.tp_flags &= ~Py_TPFLAGS_INLINE_VALUES;python/mypy#17973 (comment)
henryiii commentedon May 10, 2025
The problem occurs when you make the class, then try to assign a new dictionary to
__dict__(which is what pickle does).I tried removing the
Py_TPFLAGS_INLINE_VALUES, but it didn't do anything, and I'm not sure it should. The code here doesn't check to see if it's set, it only checks the size and then then forces it to be inline, setting it if it's not already set. Maybe it should check forPy_TPFLAGS_MANAGED_DICTbesides just checking the size? This patch fixes it for me:Edit: this never fails to be hit, actually, so is not the right solution.
henryiii commentedon May 10, 2025
I have a MWE without pybind11 or mypyc:
src/main.c:
pyproject.toml:
CMakeLists.txt:
example.py:
Edit: Fixed, I forgot
PyObject_VAR_HEAD. This reproduces the bug: with Python 3.13, it works, 3.14b1, it fails.I did test that if we change the pickle implementation to do
__dict__.updateinstead, it works. So it's just__dict__ = {...}that's broken;__dict__updates, but the attributes don't update.There is custom code in
PyObject_GenericSetDictthat is supposed to be handling the inline case, but maybe it wasn't hit before and is faulty?henryiii commentedon May 12, 2025
Opened a new issue.
encukou commentedon May 12, 2025
The new public API,
Py_TPFLAGS_INLINE_VALUES, still needs documentation.stefanor commentedon Jun 19, 2025
One of these commits (c32dc47) caused a regression: #130327 (and #135552). Among the affected is OpenStack Nova.